[FL-2052] New build system based on scons (#1269)
This commit is contained in:
47
site_scons/cc.scons
Normal file
47
site_scons/cc.scons
Normal file
@@ -0,0 +1,47 @@
|
||||
Import("ENV")
|
||||
|
||||
|
||||
ENV.AppendUnique(
|
||||
CFLAGS=[
|
||||
"-std=gnu17",
|
||||
],
|
||||
CXXFLAGS=[
|
||||
"-std=c++17",
|
||||
"-fno-rtti",
|
||||
"-fno-use-cxa-atexit",
|
||||
"-fno-exceptions",
|
||||
"-fno-threadsafe-statics",
|
||||
],
|
||||
CCFLAGS=[
|
||||
"-mcpu=cortex-m4",
|
||||
"-mfloat-abi=hard",
|
||||
"-mfpu=fpv4-sp-d16",
|
||||
"-mthumb",
|
||||
# "-MMD",
|
||||
# "-MP",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Werror",
|
||||
"-Wno-address-of-packed-member",
|
||||
"-Wredundant-decls",
|
||||
"-Wdouble-promotion",
|
||||
"-fdata-sections",
|
||||
"-ffunction-sections",
|
||||
"-fsingle-precision-constant",
|
||||
"-fno-math-errno",
|
||||
"-fstack-usage",
|
||||
"-g",
|
||||
# "-Wno-stringop-overread",
|
||||
# "-Wno-stringop-overflow",
|
||||
],
|
||||
CPPDEFINES=[
|
||||
"_GNU_SOURCE",
|
||||
],
|
||||
LINKFLAGS=[
|
||||
"-mcpu=cortex-m4",
|
||||
"-mfloat-abi=hard",
|
||||
"-mfpu=fpv4-sp-d16",
|
||||
"-mlittle-endian",
|
||||
"-mthumb",
|
||||
],
|
||||
)
|
202
site_scons/commandline.scons
Normal file
202
site_scons/commandline.scons
Normal file
@@ -0,0 +1,202 @@
|
||||
# Commandline options
|
||||
|
||||
# To build updater-related targets, you need to set this option
|
||||
AddOption(
|
||||
"--with-updater",
|
||||
dest="fullenv",
|
||||
action="store_true",
|
||||
help="Full firmware environment",
|
||||
)
|
||||
|
||||
AddOption(
|
||||
"--options",
|
||||
dest="optionfile",
|
||||
type="string",
|
||||
nargs=1,
|
||||
action="store",
|
||||
default="fbt_options.py",
|
||||
help="Enviroment option file",
|
||||
)
|
||||
|
||||
AddOption(
|
||||
"--extra-int-apps",
|
||||
action="store",
|
||||
dest="extra_int_apps",
|
||||
default="",
|
||||
help="List of applications to add to firmare's built-ins. Also see FIRMWARE_APP_SET and FIRMWARE_APPS",
|
||||
)
|
||||
|
||||
AddOption(
|
||||
"--extra-ext-apps",
|
||||
action="store",
|
||||
dest="extra_ext_apps",
|
||||
default="",
|
||||
help="List of applications to forcefully build as standalone .elf",
|
||||
)
|
||||
|
||||
|
||||
# Construction environment variables
|
||||
|
||||
vars = Variables(GetOption("optionfile"), ARGUMENTS)
|
||||
|
||||
vars.AddVariables(
|
||||
BoolVariable(
|
||||
"VERBOSE",
|
||||
help="Print full commands",
|
||||
default=False,
|
||||
),
|
||||
BoolVariable(
|
||||
"FORCE",
|
||||
help="Force target action (for supported targets)",
|
||||
default=False,
|
||||
),
|
||||
BoolVariable(
|
||||
"DEBUG",
|
||||
help="Enable debug build",
|
||||
default=True,
|
||||
),
|
||||
BoolVariable(
|
||||
"COMPACT",
|
||||
help="Optimize for size",
|
||||
default=False,
|
||||
),
|
||||
EnumVariable(
|
||||
"TARGET_HW",
|
||||
help="Hardware target",
|
||||
default="7",
|
||||
allowed_values=[
|
||||
"7",
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
vars.Add(
|
||||
"DIST_SUFFIX",
|
||||
help="Suffix for binaries in build output for dist targets",
|
||||
default="local",
|
||||
)
|
||||
|
||||
vars.Add(
|
||||
"UPDATE_VERSION_STRING",
|
||||
help="Version string for updater package",
|
||||
default="${DIST_SUFFIX}",
|
||||
)
|
||||
|
||||
|
||||
vars.Add(
|
||||
"COPRO_CUBE_VERSION",
|
||||
help="Cube version",
|
||||
default="",
|
||||
)
|
||||
|
||||
vars.Add(
|
||||
"COPRO_STACK_ADDR",
|
||||
help="Core2 Firmware address",
|
||||
default="0",
|
||||
)
|
||||
|
||||
vars.Add(
|
||||
"COPRO_STACK_BIN",
|
||||
help="Core2 Firmware file name",
|
||||
default="",
|
||||
)
|
||||
|
||||
vars.Add(
|
||||
"COPRO_DISCLAIMER",
|
||||
help="Value to pass to bundling script to confirm dangerous operations",
|
||||
default="",
|
||||
)
|
||||
|
||||
vars.AddVariables(
|
||||
PathVariable(
|
||||
"COPRO_OB_DATA",
|
||||
help="Path to OB reference data",
|
||||
validator=PathVariable.PathIsFile,
|
||||
default="",
|
||||
),
|
||||
PathVariable(
|
||||
"COPRO_STACK_BIN_DIR",
|
||||
help="Path to ST-provided stacks",
|
||||
validator=PathVariable.PathIsDir,
|
||||
default="",
|
||||
),
|
||||
PathVariable(
|
||||
"COPRO_CUBE_DIR",
|
||||
help="Path to Cube root",
|
||||
validator=PathVariable.PathIsDir,
|
||||
default="",
|
||||
),
|
||||
EnumVariable(
|
||||
"COPRO_STACK_TYPE",
|
||||
help="Core2 stack type",
|
||||
default="ble_light",
|
||||
allowed_values=[
|
||||
"ble_full",
|
||||
"ble_light",
|
||||
"ble_basic",
|
||||
],
|
||||
),
|
||||
PathVariable(
|
||||
"SVD_FILE",
|
||||
help="Path to SVD file",
|
||||
validator=PathVariable.PathIsFile,
|
||||
default="",
|
||||
),
|
||||
PathVariable(
|
||||
"OTHER_ELF",
|
||||
help="Path to prebuilt ELF file to debug",
|
||||
validator=PathVariable.PathAccept,
|
||||
default="",
|
||||
),
|
||||
)
|
||||
|
||||
vars.Add(
|
||||
"FBT_TOOLCHAIN_VERSIONS",
|
||||
help="Whitelisted toolchain versions (leave empty for no check)",
|
||||
default=tuple(),
|
||||
)
|
||||
|
||||
vars.Add(
|
||||
"OPENOCD_OPTS",
|
||||
help="Options to pass to OpenOCD",
|
||||
default="",
|
||||
)
|
||||
|
||||
vars.Add(
|
||||
"UPDATE_SPLASH",
|
||||
help="Directory name with slideshow frames to render after installing update package",
|
||||
default="update_default",
|
||||
)
|
||||
|
||||
|
||||
vars.Add(
|
||||
"FIRMWARE_APPS",
|
||||
help="Map of (configuration_name->application_list)",
|
||||
default={
|
||||
"default": (
|
||||
"crypto_start",
|
||||
# Svc
|
||||
"basic_services",
|
||||
# Apps
|
||||
"basic_apps",
|
||||
"updater_app",
|
||||
"archive",
|
||||
# Settings
|
||||
"passport",
|
||||
"system_settings",
|
||||
"about",
|
||||
# Plugins
|
||||
"basic_plugins",
|
||||
# Debug
|
||||
"debug_apps",
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
vars.Add(
|
||||
"FIRMWARE_APP_SET",
|
||||
help="Application set to use from FIRMWARE_APPS",
|
||||
default="default",
|
||||
)
|
||||
|
||||
Return("vars")
|
82
site_scons/environ.scons
Normal file
82
site_scons/environ.scons
Normal file
@@ -0,0 +1,82 @@
|
||||
import SCons
|
||||
from SCons.Platform import TempFileMunge
|
||||
from fbt import util
|
||||
|
||||
import os
|
||||
import multiprocessing
|
||||
|
||||
Import("VAR_ENV")
|
||||
|
||||
forward_os_env = {
|
||||
# Import PATH from OS env - scons doesn't do that by default
|
||||
"PATH": os.environ["PATH"],
|
||||
}
|
||||
# Proxying CI environment to child processes & scripts
|
||||
for env_value_name in ("WORKFLOW_BRANCH_OR_TAG", "DIST_SUFFIX", "HOME", "APPDATA"):
|
||||
if environ_value := os.environ.get(env_value_name, None):
|
||||
forward_os_env[env_value_name] = environ_value
|
||||
|
||||
|
||||
coreenv = VAR_ENV.Clone(
|
||||
tools=[
|
||||
(
|
||||
"crosscc",
|
||||
{
|
||||
"toolchain_prefix": "arm-none-eabi-",
|
||||
"versions": VAR_ENV["FBT_TOOLCHAIN_VERSIONS"],
|
||||
},
|
||||
),
|
||||
"python3",
|
||||
"sconsmodular",
|
||||
"sconsrecursiveglob",
|
||||
"ccache",
|
||||
],
|
||||
TEMPFILE=TempFileMunge,
|
||||
MAXLINELENGTH=2048,
|
||||
PROGSUFFIX=".elf",
|
||||
ENV=forward_os_env,
|
||||
)
|
||||
|
||||
# If DIST_SUFFIX is set in environment, is has precedence (set by CI)
|
||||
if os_suffix := os.environ.get("DIST_SUFFIX", None):
|
||||
coreenv.Replace(
|
||||
DIST_SUFFIX=os_suffix,
|
||||
)
|
||||
|
||||
# print(coreenv.Dump())
|
||||
if not coreenv["VERBOSE"]:
|
||||
coreenv.SetDefault(
|
||||
CCCOMSTR="\tCC\t${SOURCE}",
|
||||
CXXCOMSTR="\tCPP\t${SOURCE}",
|
||||
ASCOMSTR="\tASM\t${SOURCE}",
|
||||
ARCOMSTR="\tAR\t${TARGET}",
|
||||
RANLIBCOMSTR="\tRANLIB\t${TARGET}",
|
||||
LINKCOMSTR="\tLINK\t${TARGET}",
|
||||
INSTALLSTR="\tINSTALL\t${TARGET}",
|
||||
APPSCOMSTR="\tAPPS\t${TARGET}",
|
||||
VERSIONCOMSTR="\tVERSION\t${TARGET}",
|
||||
STRIPCOMSTR="\tSTRIP\t${TARGET}",
|
||||
OBJDUMPCOMSTR="\tOBJDUMP\t${TARGET}",
|
||||
# GDBCOMSTR="\tGDB\t${SOURCE}",
|
||||
# GDBPYCOMSTR="\tGDB-PY\t${SOURCE}",
|
||||
)
|
||||
|
||||
# Default value for commandline options
|
||||
|
||||
SetOption("num_jobs", multiprocessing.cpu_count())
|
||||
# Avoiding re-scan of all sources on every startup
|
||||
SetOption("implicit_cache", True)
|
||||
SetOption("implicit_deps_unchanged", True)
|
||||
# More aggressive caching
|
||||
SetOption("max_drift", 1)
|
||||
# Random task queue - to discover isses with build logic faster
|
||||
# SetOption("random", 1)
|
||||
|
||||
|
||||
# Setting up temp file parameters - to overcome command line length limits
|
||||
coreenv["TEMPFILEARGESCFUNC"] = util.tempfile_arg_esc_func
|
||||
util.wrap_tempfile(coreenv, "LINKCOM")
|
||||
util.wrap_tempfile(coreenv, "ARCOM")
|
||||
|
||||
|
||||
Return("coreenv")
|
0
site_scons/fbt/__init__.py
Normal file
0
site_scons/fbt/__init__.py
Normal file
243
site_scons/fbt/appmanifest.py
Normal file
243
site_scons/fbt/appmanifest.py
Normal file
@@ -0,0 +1,243 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
from enum import Enum
|
||||
import os
|
||||
|
||||
|
||||
class FlipperManifestException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class FlipperAppType(Enum):
|
||||
SERVICE = "Service"
|
||||
SYSTEM = "System"
|
||||
APP = "App"
|
||||
PLUGIN = "Plugin"
|
||||
DEBUG = "Debug"
|
||||
ARCHIVE = "Archive"
|
||||
SETTINGS = "Settings"
|
||||
STARTUP = "StartupHook"
|
||||
EXTERNAL = "External"
|
||||
METAPACKAGE = "Package"
|
||||
|
||||
|
||||
@dataclass
|
||||
class FlipperApplication:
|
||||
appid: str
|
||||
apptype: FlipperAppType
|
||||
name: Optional[str] = None
|
||||
entry_point: Optional[str] = None
|
||||
flags: List[str] = field(default_factory=lambda: ["Default"])
|
||||
cdefines: List[str] = field(default_factory=list)
|
||||
requires: List[str] = field(default_factory=list)
|
||||
conflicts: List[str] = field(default_factory=list)
|
||||
provides: List[str] = field(default_factory=list)
|
||||
stack_size: int = 2048
|
||||
icon: Optional[str] = None
|
||||
order: int = 0
|
||||
_appdir: Optional[str] = None
|
||||
|
||||
|
||||
class AppManager:
|
||||
def __init__(self):
|
||||
self.known_apps = {}
|
||||
|
||||
def get(self, appname: str):
|
||||
try:
|
||||
return self.known_apps[appname]
|
||||
except KeyError as _:
|
||||
raise FlipperManifestException(
|
||||
f"Missing application manifest for '{appname}'"
|
||||
)
|
||||
|
||||
def load_manifest(self, app_manifest_path: str, app_dir_name: str):
|
||||
if not os.path.exists(app_manifest_path):
|
||||
raise FlipperManifestException(
|
||||
f"App manifest not found at path {app_manifest_path}"
|
||||
)
|
||||
# print("Loading", app_manifest_path)
|
||||
|
||||
app_manifests = []
|
||||
|
||||
def App(*args, **kw):
|
||||
nonlocal app_manifests
|
||||
app_manifests.append(FlipperApplication(*args, **kw, _appdir=app_dir_name))
|
||||
|
||||
with open(app_manifest_path, "rt") as manifest_file:
|
||||
exec(manifest_file.read())
|
||||
|
||||
if len(app_manifests) == 0:
|
||||
raise FlipperManifestException(
|
||||
f"App manifest '{app_manifest_path}' is malformed"
|
||||
)
|
||||
|
||||
# print("Built", app_manifests)
|
||||
for app in app_manifests:
|
||||
self._add_known_app(app)
|
||||
|
||||
def _add_known_app(self, app: FlipperApplication):
|
||||
if self.known_apps.get(app.appid, None):
|
||||
raise FlipperManifestException(f"Duplicate app declaration: {app.appid}")
|
||||
self.known_apps[app.appid] = app
|
||||
|
||||
def filter_apps(self, applist: List[str]):
|
||||
return AppBuildset(self, applist)
|
||||
|
||||
|
||||
class AppBuilderException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AppBuildset:
|
||||
BUILTIN_APP_TYPES = (
|
||||
FlipperAppType.SERVICE,
|
||||
FlipperAppType.SYSTEM,
|
||||
FlipperAppType.APP,
|
||||
FlipperAppType.PLUGIN,
|
||||
FlipperAppType.DEBUG,
|
||||
FlipperAppType.ARCHIVE,
|
||||
FlipperAppType.SETTINGS,
|
||||
FlipperAppType.STARTUP,
|
||||
)
|
||||
|
||||
def __init__(self, appmgr: AppManager, appnames: List[str]):
|
||||
self.appmgr = appmgr
|
||||
self.appnames = set(appnames)
|
||||
self._orig_appnames = appnames
|
||||
self._process_deps()
|
||||
self._check_conflicts()
|
||||
self._check_unsatisfied() # unneeded?
|
||||
self.apps = sorted(
|
||||
list(map(self.appmgr.get, self.appnames)),
|
||||
key=lambda app: app.appid,
|
||||
)
|
||||
|
||||
def _is_missing_dep(self, dep_name: str):
|
||||
return dep_name not in self.appnames
|
||||
|
||||
def _process_deps(self):
|
||||
while True:
|
||||
provided = []
|
||||
for app in self.appnames:
|
||||
# print(app)
|
||||
provided.extend(
|
||||
filter(
|
||||
self._is_missing_dep,
|
||||
self.appmgr.get(app).provides + self.appmgr.get(app).requires,
|
||||
)
|
||||
)
|
||||
# print("provides round", provided)
|
||||
if len(provided) == 0:
|
||||
break
|
||||
self.appnames.update(provided)
|
||||
|
||||
def _check_conflicts(self):
|
||||
conflicts = []
|
||||
for app in self.appnames:
|
||||
# print(app)
|
||||
if conflict_app_name := list(
|
||||
filter(
|
||||
lambda dep_name: dep_name in self.appnames,
|
||||
self.appmgr.get(app).conflicts,
|
||||
)
|
||||
):
|
||||
conflicts.append((app, conflict_app_name))
|
||||
|
||||
if len(conflicts):
|
||||
raise AppBuilderException(
|
||||
f"App conflicts for {', '.join(f'{conflict_dep[0]}: {conflict_dep[1]}' for conflict_dep in conflicts)}"
|
||||
)
|
||||
|
||||
def _check_unsatisfied(self):
|
||||
unsatisfied = []
|
||||
for app in self.appnames:
|
||||
if missing_dep := list(
|
||||
filter(self._is_missing_dep, self.appmgr.get(app).requires)
|
||||
):
|
||||
unsatisfied.append((app, missing_dep))
|
||||
|
||||
if len(unsatisfied):
|
||||
raise AppBuilderException(
|
||||
f"Unsatisfied dependencies for {', '.join(f'{missing_dep[0]}: {missing_dep[1]}' for missing_dep in unsatisfied)}"
|
||||
)
|
||||
|
||||
def get_apps_cdefs(self):
|
||||
cdefs = set()
|
||||
for app in self.apps:
|
||||
cdefs.update(app.cdefines)
|
||||
return sorted(list(cdefs))
|
||||
|
||||
def get_apps_of_type(self, apptype: FlipperAppType):
|
||||
return sorted(
|
||||
filter(lambda app: app.apptype == apptype, self.apps),
|
||||
key=lambda app: app.order,
|
||||
)
|
||||
|
||||
def get_builtin_app_folders(self):
|
||||
return sorted(
|
||||
set(
|
||||
app._appdir
|
||||
for app in filter(
|
||||
lambda app: app.apptype in self.BUILTIN_APP_TYPES, self.apps
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ApplicationsCGenerator:
|
||||
APP_TYPE_MAP = {
|
||||
FlipperAppType.SERVICE: ("FlipperApplication", "FLIPPER_SERVICES"),
|
||||
FlipperAppType.SYSTEM: ("FlipperApplication", "FLIPPER_SYSTEM_APPS"),
|
||||
FlipperAppType.APP: ("FlipperApplication", "FLIPPER_APPS"),
|
||||
FlipperAppType.PLUGIN: ("FlipperApplication", "FLIPPER_PLUGINS"),
|
||||
FlipperAppType.DEBUG: ("FlipperApplication", "FLIPPER_DEBUG_APPS"),
|
||||
FlipperAppType.SETTINGS: ("FlipperApplication", "FLIPPER_SETTINGS_APPS"),
|
||||
FlipperAppType.STARTUP: ("FlipperOnStartHook", "FLIPPER_ON_SYSTEM_START"),
|
||||
}
|
||||
|
||||
def __init__(self, buildset: AppBuildset):
|
||||
self.buildset = buildset
|
||||
|
||||
def get_app_ep_forward(self, app: FlipperApplication):
|
||||
if app.apptype == FlipperAppType.STARTUP:
|
||||
return f"extern void {app.entry_point}();"
|
||||
return f"extern int32_t {app.entry_point}(void* p);"
|
||||
|
||||
def get_app_descr(self, app: FlipperApplication):
|
||||
if app.apptype == FlipperAppType.STARTUP:
|
||||
return app.entry_point
|
||||
return f"""
|
||||
{{.app = {app.entry_point},
|
||||
.name = "{app.name}",
|
||||
.stack_size = {app.stack_size},
|
||||
.icon = {f"&{app.icon}" if app.icon else "NULL"},
|
||||
.flags = {'|'.join(f"FlipperApplicationFlag{flag}" for flag in app.flags)} }}"""
|
||||
|
||||
def generate(self):
|
||||
contents = ['#include "applications.h"', "#include <assets_icons.h>"]
|
||||
for apptype in self.APP_TYPE_MAP:
|
||||
contents.extend(
|
||||
map(self.get_app_ep_forward, self.buildset.get_apps_of_type(apptype))
|
||||
)
|
||||
entry_type, entry_block = self.APP_TYPE_MAP[apptype]
|
||||
contents.append(f"const {entry_type} {entry_block}[] = {{")
|
||||
contents.append(
|
||||
",\n".join(
|
||||
map(self.get_app_descr, self.buildset.get_apps_of_type(apptype))
|
||||
)
|
||||
)
|
||||
contents.append("};")
|
||||
contents.append(
|
||||
f"const size_t {entry_block}_COUNT = COUNT_OF({entry_block});"
|
||||
)
|
||||
|
||||
archive_app = self.buildset.get_apps_of_type(FlipperAppType.ARCHIVE)
|
||||
if archive_app:
|
||||
contents.extend(
|
||||
[
|
||||
self.get_app_ep_forward(archive_app[0]),
|
||||
f"const FlipperApplication FLIPPER_ARCHIVE = {self.get_app_descr(archive_app[0])};",
|
||||
]
|
||||
)
|
||||
|
||||
return "\n".join(contents)
|
19
site_scons/fbt/util.py
Normal file
19
site_scons/fbt/util.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import SCons
|
||||
from SCons.Subst import quote_spaces
|
||||
|
||||
import re
|
||||
|
||||
|
||||
WINPATHSEP_RE = re.compile(r"\\([^\"'\\]|$)")
|
||||
|
||||
|
||||
def tempfile_arg_esc_func(arg):
|
||||
arg = quote_spaces(arg)
|
||||
if SCons.Platform.platform_default() != "win32":
|
||||
return arg
|
||||
# GCC requires double Windows slashes, let's use UNIX separator
|
||||
return WINPATHSEP_RE.sub(r"/\1", arg)
|
||||
|
||||
|
||||
def wrap_tempfile(env, command):
|
||||
env[command] = '${TEMPFILE("' + env[command] + '","$' + command + 'STR")}'
|
23
site_scons/fbt/version.py
Normal file
23
site_scons/fbt/version.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import subprocess
|
||||
import datetime
|
||||
|
||||
|
||||
def get_fast_git_version_id():
|
||||
try:
|
||||
version = (
|
||||
subprocess.check_output(
|
||||
[
|
||||
"git",
|
||||
"describe",
|
||||
"--always",
|
||||
"--dirty",
|
||||
"--all",
|
||||
"--long",
|
||||
]
|
||||
)
|
||||
.strip()
|
||||
.decode()
|
||||
)
|
||||
return (version, datetime.date.today())
|
||||
except Exception as e:
|
||||
print("Failed to check for git changes", e)
|
50
site_scons/firmwareopts.scons
Normal file
50
site_scons/firmwareopts.scons
Normal file
@@ -0,0 +1,50 @@
|
||||
Import("ENV")
|
||||
|
||||
|
||||
if ENV["DEBUG"]:
|
||||
ENV.Append(
|
||||
CPPDEFINES=[
|
||||
"FURI_DEBUG",
|
||||
"NDEBUG",
|
||||
],
|
||||
CCFLAGS=[
|
||||
"-Og",
|
||||
],
|
||||
)
|
||||
elif ENV["COMPACT"]:
|
||||
ENV.Append(
|
||||
CPPDEFINES=[
|
||||
"FURI_NDEBUG",
|
||||
"NDEBUG",
|
||||
],
|
||||
CCFLAGS=[
|
||||
"-Os",
|
||||
],
|
||||
)
|
||||
else:
|
||||
ENV.Append(
|
||||
CPPDEFINES=[
|
||||
"FURI_NDEBUG",
|
||||
"NDEBUG",
|
||||
],
|
||||
CCFLAGS=[
|
||||
"-Og",
|
||||
],
|
||||
)
|
||||
|
||||
ENV.Append(
|
||||
LINKFLAGS=[
|
||||
"-Tfirmware/targets/f${TARGET_HW}/${LINKER_SCRIPT}.ld",
|
||||
],
|
||||
)
|
||||
|
||||
if ENV["FIRMWARE_BUILD_CFG"] == "updater":
|
||||
ENV.Append(
|
||||
IMAGE_BASE_ADDRESS="0x20000000",
|
||||
LINKER_SCRIPT="stm32wb55xx_ram_fw",
|
||||
)
|
||||
else:
|
||||
ENV.Append(
|
||||
IMAGE_BASE_ADDRESS="0x8000000",
|
||||
LINKER_SCRIPT="stm32wb55xx_flash",
|
||||
)
|
40
site_scons/site_init.py
Normal file
40
site_scons/site_init.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from SCons.Script import GetBuildFailures
|
||||
|
||||
import sys
|
||||
import os
|
||||
import atexit
|
||||
|
||||
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
|
||||
|
||||
|
||||
def bf_to_str(bf):
|
||||
"""Convert an element of GetBuildFailures() to a string
|
||||
in a useful way."""
|
||||
import SCons.Errors
|
||||
|
||||
if bf is None: # unknown targets product None in list
|
||||
return "(unknown tgt)"
|
||||
elif isinstance(bf, SCons.Errors.StopError):
|
||||
return str(bf)
|
||||
elif bf.node:
|
||||
return str(bf.node) + ": " + bf.errstr
|
||||
elif bf.filename:
|
||||
return bf.filename + ": " + bf.errstr
|
||||
return "unknown failure: " + bf.errstr
|
||||
|
||||
|
||||
def display_build_status():
|
||||
"""Display the build status. Called by atexit.
|
||||
Here you could do all kinds of complicated things."""
|
||||
bf = GetBuildFailures()
|
||||
if bf:
|
||||
# bf is normally a list of build failures; if an element is None,
|
||||
# it's because of a target that scons doesn't know anything about.
|
||||
failures_message = "\n".join(
|
||||
["Failed building %s" % bf_to_str(x) for x in bf if x is not None]
|
||||
)
|
||||
print("*" * 10, "ERRORS", "*" * 10)
|
||||
print(failures_message)
|
||||
|
||||
|
||||
atexit.register(display_build_status)
|
14
site_scons/site_tools/ccache.py
Normal file
14
site_scons/site_tools/ccache.py
Normal file
@@ -0,0 +1,14 @@
|
||||
def exists():
|
||||
return True
|
||||
|
||||
|
||||
def generate(env):
|
||||
if ccache := env.WhereIs("ccache"):
|
||||
env["CCACHE"] = "ccache"
|
||||
env["CC_NOCACHE"] = env["CC"]
|
||||
env["CC"] = "$CCACHE $CC_NOCACHE"
|
||||
# Tricky place: linking is done with CXX
|
||||
# Using ccache breaks it
|
||||
env["LINK"] = env["CXX"]
|
||||
env["CXX_NOCACHE"] = env["CXX"]
|
||||
env["CXX"] = "$CCACHE $CXX_NOCACHE"
|
72
site_scons/site_tools/crosscc.py
Normal file
72
site_scons/site_tools/crosscc.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from SCons.Tool import asm
|
||||
from SCons.Tool import gcc
|
||||
from SCons.Tool import gxx
|
||||
from SCons.Tool import ar
|
||||
from SCons.Tool import gnulink
|
||||
import strip
|
||||
import gdb
|
||||
import objdump
|
||||
|
||||
from SCons.Action import _subproc
|
||||
import subprocess
|
||||
|
||||
|
||||
def prefix_commands(env, command_prefix, cmd_list):
|
||||
for command in cmd_list:
|
||||
if command in env:
|
||||
env[command] = command_prefix + env[command]
|
||||
|
||||
|
||||
def _get_tool_version(env, tool):
|
||||
verstr = "version unknown"
|
||||
proc = _subproc(
|
||||
env,
|
||||
env.subst("${%s} --version" % tool),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr="devnull",
|
||||
stdin="devnull",
|
||||
universal_newlines=True,
|
||||
error="raise",
|
||||
shell=True,
|
||||
)
|
||||
if proc:
|
||||
verstr = proc.stdout.readline()
|
||||
proc.communicate()
|
||||
return verstr
|
||||
|
||||
|
||||
def generate(env, **kw):
|
||||
for orig_tool in (asm, gcc, gxx, ar, gnulink, strip, gdb, objdump):
|
||||
orig_tool.generate(env)
|
||||
env.SetDefault(
|
||||
TOOLCHAIN_PREFIX=kw.get("toolchain_prefix"),
|
||||
)
|
||||
prefix_commands(
|
||||
env,
|
||||
env.subst("$TOOLCHAIN_PREFIX"),
|
||||
[
|
||||
"AR",
|
||||
"AS",
|
||||
"CC",
|
||||
"CXX",
|
||||
"OBJCOPY",
|
||||
"RANLIB",
|
||||
"STRIP",
|
||||
"GDB",
|
||||
"GDBPY",
|
||||
"OBJDUMP",
|
||||
],
|
||||
)
|
||||
# Call CC to check version
|
||||
if whitelisted_versions := kw.get("versions", ()):
|
||||
cc_version = _get_tool_version(env, "CC")
|
||||
# print("CC version =", cc_version)
|
||||
# print(list(filter(lambda v: v in cc_version, whitelisted_versions)))
|
||||
if not any(filter(lambda v: v in cc_version, whitelisted_versions)):
|
||||
raise Exception(
|
||||
f"Toolchain version is not supported. Allowed: {whitelisted_versions}, toolchain: {cc_version} "
|
||||
)
|
||||
|
||||
|
||||
def exists(env):
|
||||
return True
|
61
site_scons/site_tools/fbt_apps.py
Normal file
61
site_scons/site_tools/fbt_apps.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from SCons.Builder import Builder
|
||||
from SCons.Action import Action
|
||||
|
||||
import SCons
|
||||
from fbt.appmanifest import FlipperAppType, AppManager, ApplicationsCGenerator
|
||||
|
||||
# Adding objects for application management to env
|
||||
# AppManager env["APPMGR"] - loads all manifests; manages list of known apps
|
||||
# AppBuildset env["APPBUILD"] - contains subset of apps, filtered for current config
|
||||
|
||||
|
||||
def LoadApplicationManifests(env):
|
||||
appmgr = env["APPMGR"] = AppManager()
|
||||
for entry in env.Glob("#/applications/*"):
|
||||
if isinstance(entry, SCons.Node.FS.Dir) and not str(entry).startswith("."):
|
||||
appmgr.load_manifest(entry.File("application.fam").abspath, entry.name)
|
||||
|
||||
|
||||
def PrepareApplicationsBuild(env):
|
||||
env["APPBUILD"] = env["APPMGR"].filter_apps(env["APPS"])
|
||||
env["APPBUILD_DUMP"] = env.Action(
|
||||
DumpApplicationConfig,
|
||||
"\tINFO\t",
|
||||
)
|
||||
|
||||
|
||||
def DumpApplicationConfig(target, source, env):
|
||||
print(f"Loaded {len(env['APPMGR'].known_apps)} app definitions.")
|
||||
print("Firmware modules configuration:")
|
||||
for apptype in FlipperAppType:
|
||||
app_sublist = env["APPBUILD"].get_apps_of_type(apptype)
|
||||
if app_sublist:
|
||||
print(
|
||||
f"{apptype.value}:\n\t",
|
||||
", ".join(app.appid for app in app_sublist),
|
||||
)
|
||||
|
||||
|
||||
def build_apps_c(target, source, env):
|
||||
target_file_name = target[0].path
|
||||
|
||||
gen = ApplicationsCGenerator(env["APPBUILD"])
|
||||
with open(target_file_name, "w") as file:
|
||||
file.write(gen.generate())
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(LoadApplicationManifests)
|
||||
env.AddMethod(PrepareApplicationsBuild)
|
||||
|
||||
env.Append(
|
||||
BUILDERS={
|
||||
"ApplicationsC": Builder(
|
||||
action=Action(build_apps_c, "${APPSCOMSTR}"),
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def exists(env):
|
||||
return True
|
151
site_scons/site_tools/fbt_assets.py
Normal file
151
site_scons/site_tools/fbt_assets.py
Normal file
@@ -0,0 +1,151 @@
|
||||
import SCons
|
||||
|
||||
from SCons.Builder import Builder
|
||||
from SCons.Action import Action
|
||||
from SCons.Node.FS import File
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
||||
def icons_emitter(target, source, env):
|
||||
target = [
|
||||
"compiled/assets_icons.c",
|
||||
"compiled/assets_icons.h",
|
||||
]
|
||||
return target, source
|
||||
|
||||
|
||||
def proto_emitter(target, source, env):
|
||||
out_path = target[0].path
|
||||
target = []
|
||||
for src in source:
|
||||
basename = os.path.splitext(src.name)[0]
|
||||
target.append(env.File(f"compiled/{basename}.pb.c"))
|
||||
target.append(env.File(f"compiled/{basename}.pb.h"))
|
||||
return target, source
|
||||
|
||||
|
||||
def dolphin_emitter(target, source, env):
|
||||
res_root_dir = source[0].Dir(env["DOLPHIN_RES_TYPE"])
|
||||
source = [res_root_dir]
|
||||
source.extend(
|
||||
env.GlobRecursive("*.*", res_root_dir),
|
||||
)
|
||||
|
||||
target_base_dir = target[0]
|
||||
env.Replace(_DOLPHIN_OUT_DIR=target[0])
|
||||
|
||||
if env["DOLPHIN_RES_TYPE"] == "external":
|
||||
target = []
|
||||
target.extend(
|
||||
map(
|
||||
lambda node: target_base_dir.File(
|
||||
res_root_dir.rel_path(node).replace(".png", ".bm")
|
||||
),
|
||||
filter(lambda node: isinstance(node, SCons.Node.FS.File), source),
|
||||
)
|
||||
)
|
||||
else:
|
||||
asset_basename = f"assets_dolphin_{env['DOLPHIN_RES_TYPE']}"
|
||||
target = [
|
||||
target_base_dir.File(asset_basename + ".c"),
|
||||
target_base_dir.File(asset_basename + ".h"),
|
||||
]
|
||||
|
||||
return target, source
|
||||
|
||||
|
||||
def _invoke_git(args, source_dir):
|
||||
cmd = ["git"]
|
||||
cmd.extend(args)
|
||||
return (
|
||||
subprocess.check_output(cmd, cwd=source_dir, stderr=subprocess.STDOUT)
|
||||
.strip()
|
||||
.decode()
|
||||
)
|
||||
|
||||
|
||||
def proto_ver_generator(target, source, env):
|
||||
target_file = target[0]
|
||||
src_dir = source[0].dir.abspath
|
||||
try:
|
||||
git_fetch = _invoke_git(
|
||||
["fetch", "--tags"],
|
||||
source_dir=src_dir,
|
||||
)
|
||||
except (subprocess.CalledProcessError, EnvironmentError) as e:
|
||||
# Not great, not terrible
|
||||
print("Git: fetch failed")
|
||||
|
||||
try:
|
||||
git_describe = _invoke_git(
|
||||
["describe", "--tags", "--abbrev=0"],
|
||||
source_dir=src_dir,
|
||||
)
|
||||
except (subprocess.CalledProcessError, EnvironmentError) as e:
|
||||
print("Git: describe failed")
|
||||
Exit("git error")
|
||||
|
||||
# print("describe=", git_describe)
|
||||
git_major, git_minor = git_describe.split(".")
|
||||
version_file_data = (
|
||||
"#pragma once",
|
||||
f"#define PROTOBUF_MAJOR_VERSION {git_major}",
|
||||
f"#define PROTOBUF_MINOR_VERSION {git_minor}",
|
||||
"",
|
||||
)
|
||||
with open(str(target_file), "wt") as file:
|
||||
file.write("\n".join(version_file_data))
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.SetDefault(
|
||||
ASSETS_COMPILER="${ROOT_DIR.abspath}/scripts/assets.py",
|
||||
NANOPB_COMPILER="${ROOT_DIR.abspath}/lib/nanopb/generator/nanopb_generator.py",
|
||||
)
|
||||
|
||||
env.Append(
|
||||
BUILDERS={
|
||||
"IconBuilder": Builder(
|
||||
action=Action(
|
||||
"${PYTHON3} ${ASSETS_COMPILER} icons ${SOURCE.posix} ${TARGET.dir.posix}",
|
||||
"${ICONSCOMSTR}",
|
||||
),
|
||||
emitter=icons_emitter,
|
||||
),
|
||||
"ProtoBuilder": Builder(
|
||||
action=Action(
|
||||
"${PYTHON3} ${NANOPB_COMPILER} -q -I${SOURCE.dir.posix} -D${TARGET.dir.posix} ${SOURCES.posix}",
|
||||
"${PROTOCOMSTR}",
|
||||
),
|
||||
emitter=proto_emitter,
|
||||
suffix=".pb.c",
|
||||
src_suffix=".proto",
|
||||
),
|
||||
"DolphinSymBuilder": Builder(
|
||||
action=Action(
|
||||
'${PYTHON3} ${ASSETS_COMPILER} dolphin -s dolphin_${DOLPHIN_RES_TYPE} "${SOURCE}" "${_DOLPHIN_OUT_DIR}"',
|
||||
"${DOLPHINCOMSTR}",
|
||||
),
|
||||
emitter=dolphin_emitter,
|
||||
),
|
||||
"DolphinExtBuilder": Builder(
|
||||
action=Action(
|
||||
'${PYTHON3} ${ASSETS_COMPILER} dolphin "${SOURCE}" "${_DOLPHIN_OUT_DIR}"',
|
||||
"${DOLPHINCOMSTR}",
|
||||
),
|
||||
emitter=dolphin_emitter,
|
||||
),
|
||||
"ProtoVerBuilder": Builder(
|
||||
action=Action(
|
||||
proto_ver_generator,
|
||||
"${PBVERCOMSTR}",
|
||||
),
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def exists(env):
|
||||
return True
|
104
site_scons/site_tools/fbt_dist.py
Normal file
104
site_scons/site_tools/fbt_dist.py
Normal file
@@ -0,0 +1,104 @@
|
||||
from SCons.Builder import Builder
|
||||
from SCons.Action import Action
|
||||
from SCons.Script import Mkdir
|
||||
|
||||
|
||||
def get_variant_dirname(env, project=None):
|
||||
parts = [f"f{env['TARGET_HW']}"]
|
||||
if project:
|
||||
parts.append(project)
|
||||
|
||||
suffix = ""
|
||||
if env["DEBUG"]:
|
||||
suffix += "D"
|
||||
if env["COMPACT"]:
|
||||
suffix += "C"
|
||||
if suffix:
|
||||
parts.append(suffix)
|
||||
|
||||
return "-".join(parts)
|
||||
|
||||
|
||||
def create_fw_build_targets(env, configuration_name):
|
||||
flavor = get_variant_dirname(env, configuration_name)
|
||||
build_dir = env.Dir("build").Dir(flavor).abspath
|
||||
return env.SConscript(
|
||||
"firmware.scons",
|
||||
variant_dir=build_dir,
|
||||
duplicate=0,
|
||||
exports={
|
||||
"ENV": env,
|
||||
"fw_build_meta": {
|
||||
"type": configuration_name,
|
||||
"flavor": flavor,
|
||||
"build_dir": build_dir,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def AddFwProject(env, base_env, fw_type, fw_env_key):
|
||||
project_env = env[fw_env_key] = create_fw_build_targets(base_env, fw_type)
|
||||
env.Append(
|
||||
DIST_PROJECTS=[
|
||||
project_env["FW_FLAVOR"],
|
||||
],
|
||||
DIST_DEPENDS=[
|
||||
project_env["FW_ARTIFACTS"],
|
||||
],
|
||||
)
|
||||
return project_env
|
||||
|
||||
|
||||
def AddDebugTarget(env, targetenv, force_flash=True):
|
||||
pseudo_name = f"debug.{targetenv.subst('$FIRMWARE_BUILD_CFG')}.pseudo"
|
||||
debug_target = env.GDBPy(
|
||||
pseudo_name,
|
||||
targetenv["FW_ELF"],
|
||||
GDBPYOPTS='-ex "source debug/FreeRTOS/FreeRTOS.py" '
|
||||
'-ex "source debug/PyCortexMDebug/PyCortexMDebug.py" '
|
||||
'-ex "svd_load ${SVD_FILE}" '
|
||||
'-ex "compare-sections"',
|
||||
)
|
||||
if force_flash:
|
||||
env.Depends(debug_target, targetenv["FW_FLASH"])
|
||||
env.Pseudo(pseudo_name)
|
||||
env.AlwaysBuild(debug_target)
|
||||
return debug_target
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(AddFwProject)
|
||||
env.AddMethod(AddDebugTarget)
|
||||
env.SetDefault(
|
||||
COPRO_MCU_FAMILY="STM32WB5x",
|
||||
)
|
||||
env.Append(
|
||||
BUILDERS={
|
||||
"DistBuilder": Builder(
|
||||
action=Action(
|
||||
'${PYTHON3} ${ROOT_DIR.abspath}/scripts/sconsdist.py copy -p ${DIST_PROJECTS} -s "${DIST_SUFFIX}" ${DIST_EXTRA}',
|
||||
"${DISTCOMSTR}",
|
||||
),
|
||||
),
|
||||
"CoproBuilder": Builder(
|
||||
action=Action(
|
||||
[
|
||||
Mkdir("$TARGET"),
|
||||
"${PYTHON3} ${ROOT_DIR.abspath}/scripts/assets.py "
|
||||
"copro ${COPRO_CUBE_DIR} "
|
||||
"${TARGET} ${COPRO_MCU_FAMILY} "
|
||||
"--cube_ver=${COPRO_CUBE_VERSION} "
|
||||
"--stack_type=${COPRO_STACK_TYPE} "
|
||||
'--stack_file="${COPRO_STACK_BIN}" '
|
||||
"--stack_addr=${COPRO_STACK_ADDR} ",
|
||||
],
|
||||
"",
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def exists(env):
|
||||
return True
|
30
site_scons/site_tools/fbt_extapps.py
Normal file
30
site_scons/site_tools/fbt_extapps.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import os
|
||||
|
||||
|
||||
def BuildAppElf(env, app):
|
||||
work_dir = env.subst("$EXT_APPS_WORK_DIR")
|
||||
app_target_name = os.path.join(work_dir, app.appid)
|
||||
app_alias = f"{env['FIRMWARE_BUILD_CFG']}_{app.appid}"
|
||||
app_elf = env.Program(
|
||||
app_target_name,
|
||||
env.GlobRecursive("*.c*", os.path.join(work_dir, app._appdir)),
|
||||
APP_ENTRY=app.entry_point,
|
||||
)
|
||||
app_elf_dump = env.ObjDump(app_target_name)
|
||||
env.Alias(f"{app_alias}_list", app_elf_dump)
|
||||
|
||||
app_stripped_elf = env.ELFStripper(
|
||||
os.path.join(env.subst("$PLUGIN_ELF_DIR"), app.appid), app_elf
|
||||
)
|
||||
env.Alias(app_alias, app_stripped_elf)
|
||||
return app_stripped_elf
|
||||
|
||||
|
||||
def generate(env, **kw):
|
||||
env.SetDefault(EXT_APPS_WORK_DIR=kw.get("EXT_APPS_WORK_DIR", ".extapps"))
|
||||
env.VariantDir(env.subst("$EXT_APPS_WORK_DIR"), ".", duplicate=False)
|
||||
env.AddMethod(BuildAppElf)
|
||||
|
||||
|
||||
def exists(env):
|
||||
return True
|
29
site_scons/site_tools/fbt_version.py
Normal file
29
site_scons/site_tools/fbt_version.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from SCons.Builder import Builder
|
||||
from SCons.Action import Action
|
||||
|
||||
|
||||
def version_emitter(target, source, env):
|
||||
target_dir = target[0]
|
||||
target = [
|
||||
target_dir.File("version.inc.h"),
|
||||
target_dir.File("version.json"),
|
||||
]
|
||||
return target, source
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.Append(
|
||||
BUILDERS={
|
||||
"VersionBuilder": Builder(
|
||||
action=Action(
|
||||
"${PYTHON3} ${ROOT_DIR.abspath}/scripts/version.py generate -t ${TARGET_HW} -o ${TARGET.dir.posix} --dir ${ROOT_DIR}",
|
||||
"${VERSIONCOMSTR}",
|
||||
),
|
||||
emitter=version_emitter,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def exists(env):
|
||||
return True
|
52
site_scons/site_tools/fwbin.py
Normal file
52
site_scons/site_tools/fwbin.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from SCons.Builder import Builder
|
||||
from SCons.Action import Action
|
||||
import SCons
|
||||
|
||||
__OBJCOPY_ARM_BIN = "arm-none-eabi-objcopy"
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.SetDefault(
|
||||
BIN2DFU="${ROOT_DIR.abspath}/scripts/bin2dfu.py",
|
||||
OBJCOPY=__OBJCOPY_ARM_BIN, # FIXME
|
||||
)
|
||||
env.Append(
|
||||
BUILDERS={
|
||||
"HEXBuilder": Builder(
|
||||
action=Action(
|
||||
'${OBJCOPY} -O ihex "${SOURCE}" "${TARGET}"',
|
||||
"${HEXCOMSTR}",
|
||||
),
|
||||
suffix=".hex",
|
||||
src_suffix=".elf",
|
||||
),
|
||||
"BINBuilder": Builder(
|
||||
action=Action(
|
||||
'${OBJCOPY} -O binary -S "${SOURCE}" "${TARGET}"',
|
||||
"${BINCOMSTR}",
|
||||
),
|
||||
suffix=".bin",
|
||||
src_suffix=".elf",
|
||||
),
|
||||
"DFUBuilder": Builder(
|
||||
action=Action(
|
||||
'${PYTHON3} ${BIN2DFU} -i "${SOURCE}" -o "${TARGET}" -a ${IMAGE_BASE_ADDRESS} -l "Flipper Zero F${TARGET_HW}"',
|
||||
"${DFUCOMSTR}",
|
||||
),
|
||||
suffix=".dfu",
|
||||
src_suffix=".bin",
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def exists(env):
|
||||
try:
|
||||
return env["OBJCOPY"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if objcopy := env.WhereIs(__OBJCOPY_ARM_BIN):
|
||||
return objcopy
|
||||
|
||||
raise SCons.Errors.StopError("Could not detect objcopy for arm")
|
33
site_scons/site_tools/gdb.py
Normal file
33
site_scons/site_tools/gdb.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from SCons.Builder import Builder
|
||||
from SCons.Action import Action
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.SetDefault(
|
||||
GDB="gdb",
|
||||
GDBPY="gdb-py",
|
||||
GDBOPTS="",
|
||||
GDBPYOPTS="",
|
||||
GDBCOM="$GDB $GDBOPTS $SOURCES", # no $TARGET
|
||||
GDBPYCOM="$GDBPY $GDBOPTS $GDBPYOPTS $SOURCES", # no $TARGET
|
||||
)
|
||||
env.Append(
|
||||
BUILDERS={
|
||||
"GDB": Builder(
|
||||
action=Action(
|
||||
"${GDBCOM}",
|
||||
"${GDBCOMSTR}",
|
||||
),
|
||||
),
|
||||
"GDBPy": Builder(
|
||||
action=Action(
|
||||
"${GDBPYCOM}",
|
||||
"${GDBPYCOMSTR}",
|
||||
),
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def exists(env):
|
||||
return True
|
26
site_scons/site_tools/objdump.py
Normal file
26
site_scons/site_tools/objdump.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from SCons.Builder import Builder
|
||||
from SCons.Action import Action
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.SetDefault(
|
||||
OBJDUMP="objdump",
|
||||
OBJDUMPFLAGS=[],
|
||||
OBJDUMPCOM="$OBJDUMP $OBJDUMPFLAGS -S $SOURCES > $TARGET",
|
||||
)
|
||||
env.Append(
|
||||
BUILDERS={
|
||||
"ObjDump": Builder(
|
||||
action=Action(
|
||||
"${OBJDUMPCOM}",
|
||||
"${OBJDUMPCOMSTR}",
|
||||
),
|
||||
suffix=".lst",
|
||||
src_suffix=".elf",
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def exists(env):
|
||||
return True
|
48
site_scons/site_tools/openocd.py
Normal file
48
site_scons/site_tools/openocd.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from SCons.Builder import Builder
|
||||
from SCons.Action import Action
|
||||
from SCons.Defaults import Touch
|
||||
import SCons
|
||||
|
||||
__OPENOCD_BIN = "openocd"
|
||||
|
||||
_oocd_action = Action(
|
||||
"${OPENOCD} ${OPENOCD_OPTS} ${OPENOCD_COMMAND}",
|
||||
"${OOCDCOMSTR}",
|
||||
)
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.SetDefault(
|
||||
OPENOCD=__OPENOCD_BIN,
|
||||
OPENOCD_OPTS="",
|
||||
OPENOCD_COMMAND="",
|
||||
OOCDCOMSTR="",
|
||||
)
|
||||
|
||||
env.Append(
|
||||
BUILDERS={
|
||||
"OOCDFlashCommand": Builder(
|
||||
action=[
|
||||
_oocd_action,
|
||||
Touch("${TARGET}"),
|
||||
],
|
||||
suffix=".flash",
|
||||
src_suffix=".bin",
|
||||
),
|
||||
"OOCDCommand": Builder(
|
||||
action=_oocd_action,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def exists(env):
|
||||
try:
|
||||
return env["OPENOCD"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if openocd := env.WhereIs(__OPENOCD_BIN):
|
||||
return openocd
|
||||
|
||||
raise SCons.Errors.StopError("Could not detect openocd")
|
13
site_scons/site_tools/python3.py
Normal file
13
site_scons/site_tools/python3.py
Normal file
@@ -0,0 +1,13 @@
|
||||
def generate(env):
|
||||
py_name = "python3"
|
||||
if env["PLATFORM"] == "win32":
|
||||
# On Windows, Python 3 executable is usually just "python"
|
||||
py_name = "python"
|
||||
|
||||
env.SetDefault(
|
||||
PYTHON3=py_name,
|
||||
)
|
||||
|
||||
|
||||
def exists(env):
|
||||
return True
|
38
site_scons/site_tools/sconsmodular.py
Normal file
38
site_scons/site_tools/sconsmodular.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import posixpath
|
||||
import os
|
||||
|
||||
|
||||
def BuildModule(env, module):
|
||||
src_dir = str(env.Dir(".").srcdir or os.getcwd())
|
||||
module_sconscript = posixpath.join(src_dir, module, "SConscript")
|
||||
if not os.path.exists(module_sconscript):
|
||||
module_sconscript = posixpath.join(src_dir, f"{module}.scons")
|
||||
if not os.path.exists(module_sconscript):
|
||||
print(f"Cannot build module {module}: scons file not found")
|
||||
Exit(2)
|
||||
|
||||
return env.SConscript(
|
||||
module_sconscript,
|
||||
variant_dir=posixpath.join(env.subst("$BUILD_DIR"), module),
|
||||
duplicate=0,
|
||||
)
|
||||
|
||||
|
||||
def BuildModules(env, modules):
|
||||
result = []
|
||||
for module in modules:
|
||||
build_res = env.BuildModule(module)
|
||||
# print("module ", module, build_res)
|
||||
if build_res is None:
|
||||
continue
|
||||
result.append(build_res)
|
||||
return result
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(BuildModule)
|
||||
env.AddMethod(BuildModules)
|
||||
|
||||
|
||||
def exists(env):
|
||||
return True
|
25
site_scons/site_tools/sconsrecursiveglob.py
Normal file
25
site_scons/site_tools/sconsrecursiveglob.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import SCons
|
||||
|
||||
|
||||
def GlobRecursive(env, pattern, node=".", exclude=None):
|
||||
results = []
|
||||
if isinstance(node, str):
|
||||
node = env.Dir(node)
|
||||
for f in node.glob("*", source=True, exclude=exclude):
|
||||
if isinstance(f, SCons.Node.FS.Dir):
|
||||
results += env.GlobRecursive(pattern, f, exclude)
|
||||
results += node.glob(
|
||||
pattern,
|
||||
source=True,
|
||||
exclude=exclude,
|
||||
)
|
||||
# print(f"Glob for {pattern} from {node}: {results}")
|
||||
return results
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(GlobRecursive)
|
||||
|
||||
|
||||
def exists(env):
|
||||
return True
|
26
site_scons/site_tools/strip.py
Normal file
26
site_scons/site_tools/strip.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from SCons.Builder import Builder
|
||||
from SCons.Action import Action
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.SetDefault(
|
||||
STRIP="strip",
|
||||
STRIPFLAGS=[],
|
||||
STRIPCOM="$STRIP $STRIPFLAGS $SOURCES -o $TARGET",
|
||||
)
|
||||
env.Append(
|
||||
BUILDERS={
|
||||
"ELFStripper": Builder(
|
||||
action=Action(
|
||||
"${STRIPCOM}",
|
||||
"${STRIPCOMSTR}",
|
||||
),
|
||||
suffix=".elf",
|
||||
src_suffix=".elf",
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def exists(env):
|
||||
return True
|
Reference in New Issue
Block a user