1
mirror of https://github.com/flipperdevices/flipperzero-firmware.git synced 2025-12-12 04:41:26 +04:00
Files
flipperzero-firmware/scripts/ufbt/SConstruct
Silent 4039ccbcca fbt/ufbt: Ensure POSIX paths are passed to GDB on all platforms (#3360)
* fbt/ufbt: Ensure POSIX paths are passed to GDB on all platforms
  GDB heavily dislikes forward slashes from Windows paths and strips
  them internally instead of normalizing them. Account for this by
  passing POSIX paths explicitly.
* fbt: different approach for posix path handling
* fbt, ufbt: further fixes for path handling
* fbt: explicit path stringification
* linter fixes

Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: hedger <hedger@nanode.su>
Co-authored-by: あく <alleteam@gmail.com>
2024-03-21 07:36:38 +09:00

509 lines
14 KiB
Python

import multiprocessing
import os
import pathlib
from SCons.Errors import UserError
from SCons.Node import FS
from SCons.Platform import TempFileMunge
SetOption("num_jobs", multiprocessing.cpu_count())
SetOption("max_drift", 1)
# SetOption("silent", False)
ufbt_state_dir = Dir(os.environ.get("UFBT_STATE_DIR", "#.ufbt"))
ufbt_script_dir = Dir(os.environ.get("UFBT_SCRIPT_DIR"))
ufbt_build_dir = ufbt_state_dir.Dir("build")
ufbt_current_sdk_dir = ufbt_state_dir.Dir("current")
SConsignFile(ufbt_state_dir.File(".sconsign.dblite").abspath)
ufbt_variables = SConscript("commandline.scons")
forward_os_env = {
# Import PATH from OS env - scons doesn't do that by default
"PATH": os.environ["PATH"],
}
# Core environment init - loads SDK state, sets up paths, etc.
core_env = Environment(
variables=ufbt_variables,
UFBT_STATE_DIR=ufbt_state_dir,
UFBT_CURRENT_SDK_DIR=ufbt_current_sdk_dir,
UFBT_SCRIPT_DIR=ufbt_script_dir,
toolpath=[ufbt_current_sdk_dir.Dir("scripts/ufbt/site_tools")],
tools=[
"ufbt_state",
("ufbt_help", {"vars": ufbt_variables}),
],
)
core_env.Append(CPPDEFINES=GetOption("extra_defines"))
# Now we can import stuff bundled with SDK - it was added to sys.path by ufbt_state
from fbt.appmanifest import FlipperApplication, FlipperAppType
from fbt.sdk.cache import SdkCache
from fbt.util import (
FORWARDED_ENV_VARIABLES,
PosixPathWrapper,
resolve_real_dir_node,
single_quote,
tempfile_arg_esc_func,
wrap_tempfile,
)
variables_to_forward = list(FORWARDED_ENV_VARIABLES)
if proxy_env := GetOption("proxy_env"):
variables_to_forward.extend(proxy_env.split(","))
for env_value_name in variables_to_forward:
if environ_value := os.environ.get(env_value_name, None):
forward_os_env[env_value_name] = environ_value
# Base environment with all tools loaded from SDK
env = core_env.Clone(
ENV=forward_os_env,
toolpath=[core_env["FBT_SCRIPT_DIR"].Dir("fbt_tools")],
tools=[
"fbt_tweaks",
(
"crosscc",
{
"toolchain_prefix": "arm-none-eabi-",
"versions": (" 12.3.", " 13.2."),
},
),
"fwbin",
"python3",
"sconsrecursiveglob",
"sconsmodular",
"ccache",
"fbt_apps",
"fbt_extapps",
"fbt_assets",
"fbt_envhooks",
("compilation_db", {"COMPILATIONDB_COMSTR": "\tCDB\t${TARGET}"}),
],
FBT_FAP_DEBUG_ELF_ROOT=ufbt_build_dir,
POSIXPATH=PosixPathWrapper,
TEMPFILE=TempFileMunge,
MAXLINELENGTH=2048,
PROGSUFFIX=".elf",
TEMPFILEARGESCFUNC=tempfile_arg_esc_func,
SINGLEQUOTEFUNC=single_quote,
ABSPATHGETTERFUNC=resolve_real_dir_node,
APPS=[],
EXTRA_EXT_APPS=[],
UFBT_API_VERSION=SdkCache(
core_env.subst("$SDK_DEFINITION"), load_version_only=True
).version,
APPCHECK_COMSTR="\tAPPCHK\t${SOURCE}\n\t\tTarget: ${TARGET_HW}, API: ${UFBT_API_VERSION}",
)
wrap_tempfile(env, "LINKCOM")
wrap_tempfile(env, "ARCOM")
env.PreConfigureUfbtEnvionment()
# print(env.Dump())
# Dist env
dist_env = env.Clone(
tools=[
"fbt_dist",
"fbt_debugopts",
"openocd",
"blackmagic",
"jflash",
"textfile",
],
ENV=os.environ,
OPENOCD_OPTS=[
"-f",
"interface/stlink.cfg",
"-c",
"transport select hla_swd",
"-f",
"${POSIXPATH('$FBT_DEBUG_DIR')}/stm32wbx.cfg",
"-c",
"stm32wbx.cpu configure -rtos auto",
],
)
flash_target = dist_env.FwFlash(
dist_env["UFBT_STATE_DIR"].File("flash"),
dist_env["FW_ELF"],
)
dist_env.Alias("firmware_flash", flash_target)
dist_env.Alias("flash", flash_target)
if env["FORCE"]:
env.AlwaysBuild(flash_target)
firmware_jflash = dist_env.JFlash(
dist_env["UFBT_STATE_DIR"].File("jflash"),
dist_env["FW_BIN"],
JFLASHADDR="0x08000000",
)
dist_env.Alias("firmware_jflash", firmware_jflash)
dist_env.Alias("jflash", firmware_jflash)
if env["FORCE"]:
env.AlwaysBuild(firmware_jflash)
firmware_debug = dist_env.PhonyTarget(
"debug",
"${GDBPYCOM}",
source=dist_env["FW_ELF"],
GDBOPTS="${GDBOPTS_BASE}",
GDBREMOTE="${OPENOCD_GDB_PIPE}",
)
dist_env.PhonyTarget(
"blackmagic",
"${GDBPYCOM}",
source=dist_env["FW_ELF"],
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
GDBREMOTE="${BLACKMAGIC_ADDR}",
)
# Debug alien elf
debug_other_opts = [
"-ex",
"source ${POSIXPATH('FBT_DEBUG_DIR')}/PyCortexMDebug/PyCortexMDebug.py",
"-ex",
"source ${POSIXPATH('FBT_DEBUG_DIR')}/flipperversion.py",
"-ex",
"fw-version",
]
dist_env.PhonyTarget(
"debug_other",
"${GDBPYCOM}",
GDBOPTS="${GDBOPTS_BASE}",
GDBREMOTE="${OPENOCD_GDB_PIPE}",
GDBPYOPTS=debug_other_opts,
)
dist_env.PhonyTarget(
"debug_other_blackmagic",
"${GDBPYCOM}",
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
GDBREMOTE="${BLACKMAGIC_ADDR}",
GDBPYOPTS=debug_other_opts,
)
flash_usb_full = dist_env.UsbInstall(
dist_env["UFBT_STATE_DIR"].File("usbinstall"),
[],
)
dist_env.AlwaysBuild(flash_usb_full)
dist_env.Alias("flash_usb", flash_usb_full)
dist_env.Alias("flash_usb_full", flash_usb_full)
# App build environment
appenv = env.Clone(
CCCOM=env["CCCOM"].replace("$CFLAGS", "$CFLAGS_APP $CFLAGS"),
CXXCOM=env["CXXCOM"].replace("$CXXFLAGS", "$CXXFLAGS_APP $CXXFLAGS"),
LINKCOM=env["LINKCOM"].replace("$LINKFLAGS", "$LINKFLAGS_APP $LINKFLAGS"),
COMPILATIONDB_USE_ABSPATH=True,
)
original_app_dir = Dir(appenv.subst("$UFBT_APP_DIR"))
app_mount_point = Dir("#/app/")
app_mount_point.addRepository(original_app_dir)
appenv.LoadAppManifest(app_mount_point)
appenv.PrepareApplicationsBuild()
#######################
apps_artifacts = appenv["EXT_APPS"]
apps_to_build_as_faps = [
FlipperAppType.PLUGIN,
FlipperAppType.EXTERNAL,
FlipperAppType.MENUEXTERNAL,
]
known_extapps = [
app
for apptype in apps_to_build_as_faps
for app in appenv["APPBUILD"].get_apps_of_type(apptype, True)
]
incompatible_apps = []
for app in known_extapps:
if not app.supports_hardware_target(appenv.subst("f${TARGET_HW}")):
incompatible_apps.append(app)
continue
app_artifacts = appenv.BuildAppElf(app)
app_src_dir = resolve_real_dir_node(app_artifacts.app._appdir)
app_artifacts.installer = [
appenv.Install(app_src_dir.Dir("dist"), app_artifacts.compact),
appenv.Install(app_src_dir.Dir("dist").Dir("debug"), app_artifacts.debug),
]
if len(incompatible_apps):
print(
"WARNING: The following apps are not compatible with the current target hardware and will not be built: {}".format(
", ".join([app.name for app in incompatible_apps])
)
)
if appenv["FORCE"]:
appenv.AlwaysBuild([extapp.compact for extapp in apps_artifacts.values()])
# Final steps - target aliases
install_and_check = [
(extapp.installer, extapp.validator) for extapp in apps_artifacts.values()
]
Alias(
"faps",
install_and_check,
)
Default(install_and_check)
# Compilation database
app_cdb = appenv.CompilationDatabase(
original_app_dir.Dir(".vscode").File("compile_commands.json")
)
AlwaysBuild(app_cdb)
Precious(app_cdb)
NoClean(app_cdb)
if len(apps_artifacts):
Default(app_cdb)
Alias("cdb", app_cdb)
# launch handler
runnable_apps = appenv["APPBUILD"].get_apps_of_type(FlipperAppType.EXTERNAL, True)
app_to_launch = None
if len(runnable_apps) == 1:
app_to_launch = runnable_apps[0].appid
elif len(runnable_apps) > 1:
# more than 1 app - try to find one with matching id
app_to_launch = appenv.subst("$APPID")
def ambiguous_app_call(**kw):
raise UserError(
f"More than one app is runnable: {', '.join(app.appid for app in runnable_apps)}. Please specify an app with APPID=..."
)
if app_to_launch:
appenv.AddAppLaunchTarget(app_to_launch, "launch")
appenv.AddAppBuildTarget(app_to_launch, "build")
else:
dist_env.PhonyTarget("launch", Action(ambiguous_app_call, None))
dist_env.PhonyTarget("build", Action(ambiguous_app_call, None))
# cli handler
appenv.PhonyTarget(
"cli",
[
[
"${PYTHON3}",
"${FBT_SCRIPT_DIR}/serial_cli.py",
"-p",
"${FLIP_PORT}",
"${ARGS}",
]
],
)
# Update WiFi devboard firmware
dist_env.PhonyTarget(
"devboard_flash",
[
[
"${PYTHON3}",
"${FBT_SCRIPT_DIR}/wifi_board.py",
"${ARGS}",
]
],
)
# Linter
dist_env.PhonyTarget(
"lint",
[
[
"${PYTHON3}",
"${FBT_SCRIPT_DIR}/lint.py",
"check",
"${LINT_SOURCES}",
"${ARGS}",
]
],
source=original_app_dir.File(".clang-format"),
LINT_SOURCES=[original_app_dir],
)
dist_env.PhonyTarget(
"format",
[
[
"${PYTHON3}",
"${FBT_SCRIPT_DIR}/lint.py",
"format",
"${LINT_SOURCES}",
"${ARGS}",
]
],
source=original_app_dir.File(".clang-format"),
LINT_SOURCES=[original_app_dir],
)
# Prepare vscode environment
vscode_dist = []
project_template_dir = dist_env["UFBT_SCRIPT_ROOT"].Dir("project_template")
for template_file in project_template_dir.Dir(".vscode").glob("*"):
vscode_dist.append(
dist_env.Substfile(
original_app_dir.Dir(".vscode").File(template_file.name),
template_file,
SUBST_DICT={
"@UFBT_VSCODE_PATH_SEP@": os.path.pathsep,
"@UFBT_TOOLCHAIN_ARM_TOOLCHAIN_DIR@": pathlib.Path(
dist_env.WhereIs("arm-none-eabi-gcc")
).parent.as_posix(),
"@UFBT_TOOLCHAIN_GCC@": PosixPathWrapper.fix_path(
dist_env.WhereIs("arm-none-eabi-gcc")
),
"@UFBT_TOOLCHAIN_GDB_PY@": PosixPathWrapper.fix_path(
dist_env.WhereIs("arm-none-eabi-gdb-py3")
),
"@UFBT_TOOLCHAIN_OPENOCD@": PosixPathWrapper.fix_path(
dist_env.WhereIs("openocd")
),
"@UFBT_APP_DIR@": PosixPathWrapper.fix_path(original_app_dir.abspath),
"@UFBT_ROOT_DIR@": PosixPathWrapper.fix_path(Dir("#").abspath),
"@UFBT_DEBUG_DIR@": dist_env.subst("FBT_DEBUG_DIR"),
"@UFBT_DEBUG_ELF_DIR@": PosixPathWrapper.fix_path(
dist_env["FBT_FAP_DEBUG_ELF_ROOT"].abspath
),
"@UFBT_FIRMWARE_ELF@": PosixPathWrapper.fix_path(
dist_env["FW_ELF"].abspath
),
},
)
)
for config_file in project_template_dir.glob(".*"):
if isinstance(config_file, FS.Dir):
continue
vscode_dist.append(dist_env.Install(original_app_dir, config_file))
dist_env.Precious(vscode_dist)
dist_env.NoClean(vscode_dist)
dist_env.Alias("vscode_dist", (vscode_dist, app_cdb))
# Creating app from base template
dist_env.SetDefault(FBT_APPID=appenv.subst("$APPID") or "template")
if fbt_appid := dist_env.subst("$FBT_APPID"):
if not FlipperApplication.APP_ID_REGEX.match(fbt_appid):
raise UserError(
f"Invalid app id '{fbt_appid}'. App id must match {FlipperApplication.APP_ID_REGEX.pattern}"
)
app_template_dir = project_template_dir.Dir("app_template")
app_template_dist = []
for template_file in app_template_dir.glob("*"):
dist_file_name = dist_env.subst(template_file.name)
if template_file.name.endswith(".png"):
app_template_dist.append(
dist_env.InstallAs(original_app_dir.File(dist_file_name), template_file)
)
else:
app_template_dist.append(
dist_env.Substfile(
original_app_dir.File(dist_file_name),
template_file,
SUBST_DICT={
"@FBT_APPID@": dist_env.subst("$FBT_APPID"),
},
)
)
AddPostAction(
app_template_dist[-1],
[
Mkdir(original_app_dir.Dir("images")),
Touch(original_app_dir.Dir("images").File(".gitkeep")),
# scons' glob ignores .dot directories, so we need to copy .github manually
Copy(original_app_dir.Dir(".github"), app_template_dir.Dir(".github")),
],
)
dist_env.Precious(app_template_dist)
dist_env.NoClean(app_template_dist)
dist_env.Alias("create", app_template_dist)
dist_env.PhonyTarget(
"get_blackmagic",
"@echo $( ${BLACKMAGIC_ADDR} $)",
)
dist_env.PhonyTarget(
"get_apiversion",
"@echo $( ${UFBT_API_VERSION} $)",
)
# Dolphin animation builder. Expects "external" directory in current dir
# with animation sources & manifests. Builds & uploads them to connected Flipper
dolphin_src_dir = original_app_dir.Dir("external")
if dolphin_src_dir.exists():
dolphin_dir = ufbt_build_dir.Dir("dolphin")
dolphin_external = dist_env.DolphinExtBuilder(
ufbt_build_dir.Dir("dolphin"),
original_app_dir,
DOLPHIN_RES_TYPE="external",
)
dist_env.PhonyTarget(
"dolphin_ext",
[
[
"${PYTHON3}",
"${FBT_SCRIPT_DIR}/storage.py",
"-p",
"${FLIP_PORT}",
"send",
"${SOURCE}",
"/ext/dolphin",
"${ARGS}",
]
],
source=ufbt_build_dir.Dir("dolphin"),
)
else:
def missing_dolphin_folder(**kw):
raise UserError(f"Dolphin folder not found: {dolphin_src_dir}")
dist_env.PhonyTarget("dolphin_ext", Action(missing_dolphin_folder, None))
# print(env.Dump())
dist_env.PhonyTarget(
"env",
'@echo "FBT_TOOLCHAIN_PATH='
+ forward_os_env["FBT_TOOLCHAIN_PATH"]
+ '" source $( "${FBT_SCRIPT_DIR.abspath}/toolchain/fbtenv.sh" $)',
)
dist_env.PostConfigureUfbtEnvionment()