* fbt: added Flipper selection when multiple are connected over USB * scripts: serial_cli: added --port (-p) option
		
			
				
	
	
		
			488 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			488 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from SCons.Platform import TempFileMunge
 | 
						|
from SCons.Node import FS
 | 
						|
from SCons.Errors import UserError
 | 
						|
 | 
						|
 | 
						|
import os
 | 
						|
import multiprocessing
 | 
						|
import pathlib
 | 
						|
 | 
						|
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"],
 | 
						|
}
 | 
						|
 | 
						|
# Proxying environment to child processes & scripts
 | 
						|
variables_to_forward = [
 | 
						|
    # CI/CD variables
 | 
						|
    "WORKFLOW_BRANCH_OR_TAG",
 | 
						|
    "DIST_SUFFIX",
 | 
						|
    # Python & other tools
 | 
						|
    "HOME",
 | 
						|
    "APPDATA",
 | 
						|
    "PYTHONHOME",
 | 
						|
    "PYTHONNOUSERSITE",
 | 
						|
    "TMP",
 | 
						|
    "TEMP",
 | 
						|
    # Colors for tools
 | 
						|
    "TERM",
 | 
						|
]
 | 
						|
 | 
						|
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
 | 
						|
 | 
						|
# Core environment init - loads SDK state, sets up paths, etc.
 | 
						|
core_env = Environment(
 | 
						|
    variables=ufbt_variables,
 | 
						|
    ENV=forward_os_env,
 | 
						|
    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.util import (
 | 
						|
    tempfile_arg_esc_func,
 | 
						|
    single_quote,
 | 
						|
    extract_abs_dir,
 | 
						|
    extract_abs_dir_path,
 | 
						|
    wrap_tempfile,
 | 
						|
    path_as_posix,
 | 
						|
)
 | 
						|
from fbt.appmanifest import FlipperAppType, FlipperApplication
 | 
						|
from fbt.sdk.cache import SdkCache
 | 
						|
 | 
						|
# Base environment with all tools loaded from SDK
 | 
						|
env = core_env.Clone(
 | 
						|
    toolpath=[core_env["FBT_SCRIPT_DIR"].Dir("fbt_tools")],
 | 
						|
    tools=[
 | 
						|
        "fbt_tweaks",
 | 
						|
        (
 | 
						|
            "crosscc",
 | 
						|
            {
 | 
						|
                "toolchain_prefix": "arm-none-eabi-",
 | 
						|
                "versions": (" 10.3",),
 | 
						|
            },
 | 
						|
        ),
 | 
						|
        "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,
 | 
						|
    TEMPFILE=TempFileMunge,
 | 
						|
    MAXLINELENGTH=2048,
 | 
						|
    PROGSUFFIX=".elf",
 | 
						|
    TEMPFILEARGESCFUNC=tempfile_arg_esc_func,
 | 
						|
    SINGLEQUOTEFUNC=single_quote,
 | 
						|
    ABSPATHGETTERFUNC=extract_abs_dir_path,
 | 
						|
    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",
 | 
						|
        "${FBT_DEBUG_DIR}/stm32wbx.cfg",
 | 
						|
        "-c",
 | 
						|
        "stm32wbx.cpu configure -rtos auto",
 | 
						|
    ],
 | 
						|
)
 | 
						|
 | 
						|
openocd_target = dist_env.OpenOCDFlash(
 | 
						|
    dist_env["UFBT_STATE_DIR"].File("flash"),
 | 
						|
    dist_env["FW_BIN"],
 | 
						|
    OPENOCD_COMMAND=[
 | 
						|
        "-c",
 | 
						|
        "program ${SOURCE.posix} reset exit 0x08000000",
 | 
						|
    ],
 | 
						|
)
 | 
						|
dist_env.Alias("firmware_flash", openocd_target)
 | 
						|
dist_env.Alias("flash", openocd_target)
 | 
						|
if env["FORCE"]:
 | 
						|
    env.AlwaysBuild(openocd_target)
 | 
						|
 | 
						|
 | 
						|
firmware_jflash = dist_env.JFlash(
 | 
						|
    dist_env["UFBT_STATE_DIR"].File("jflash"),
 | 
						|
    dist_env["FW_BIN"],
 | 
						|
    JFLASHADDR="0x20000000",
 | 
						|
)
 | 
						|
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}",
 | 
						|
    FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(dist_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")),
 | 
						|
)
 | 
						|
 | 
						|
dist_env.PhonyTarget(
 | 
						|
    "blackmagic",
 | 
						|
    "${GDBPYCOM}",
 | 
						|
    source=dist_env["FW_ELF"],
 | 
						|
    GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
 | 
						|
    GDBREMOTE="${BLACKMAGIC_ADDR}",
 | 
						|
    FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(dist_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")),
 | 
						|
)
 | 
						|
 | 
						|
# Debug alien elf
 | 
						|
debug_other_opts = [
 | 
						|
    "-ex",
 | 
						|
    "source ${FBT_DEBUG_DIR}/PyCortexMDebug/PyCortexMDebug.py",
 | 
						|
    "-ex",
 | 
						|
    "source ${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,
 | 
						|
)
 | 
						|
 | 
						|
 | 
						|
dist_env.PhonyTarget(
 | 
						|
    "flash_blackmagic",
 | 
						|
    "$GDB $GDBOPTS $SOURCES $GDBFLASH",
 | 
						|
    source=dist_env["FW_ELF"],
 | 
						|
    GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
 | 
						|
    GDBREMOTE="${BLACKMAGIC_ADDR}",
 | 
						|
    GDBFLASH=[
 | 
						|
        "-ex",
 | 
						|
        "load",
 | 
						|
        "-ex",
 | 
						|
        "quit",
 | 
						|
    ],
 | 
						|
)
 | 
						|
 | 
						|
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,
 | 
						|
]
 | 
						|
 | 
						|
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 = extract_abs_dir(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
 | 
						|
 | 
						|
fwcdb = appenv.CompilationDatabase(
 | 
						|
    original_app_dir.Dir(".vscode").File("compile_commands.json")
 | 
						|
)
 | 
						|
 | 
						|
AlwaysBuild(fwcdb)
 | 
						|
Precious(fwcdb)
 | 
						|
NoClean(fwcdb)
 | 
						|
if len(apps_artifacts):
 | 
						|
    Default(fwcdb)
 | 
						|
 | 
						|
 | 
						|
# 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")
 | 
						|
else:
 | 
						|
    dist_env.PhonyTarget("launch", Action(ambiguous_app_call, None))
 | 
						|
 | 
						|
# cli handler
 | 
						|
 | 
						|
appenv.PhonyTarget(
 | 
						|
    "cli",
 | 
						|
    '${PYTHON3} "${FBT_SCRIPT_DIR}/serial_cli.py" -p ${FLIP_PORT}',
 | 
						|
)
 | 
						|
 | 
						|
# Linter
 | 
						|
 | 
						|
dist_env.PhonyTarget(
 | 
						|
    "lint",
 | 
						|
    "${PYTHON3} ${FBT_SCRIPT_DIR}/lint.py check ${LINT_SOURCES}",
 | 
						|
    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}",
 | 
						|
    source=original_app_dir.File(".clang-format"),
 | 
						|
    LINT_SOURCES=[original_app_dir],
 | 
						|
)
 | 
						|
 | 
						|
 | 
						|
# Prepare vscode environment
 | 
						|
def _path_as_posix(path):
 | 
						|
    return pathlib.Path(path).as_posix()
 | 
						|
 | 
						|
 | 
						|
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@": _path_as_posix(
 | 
						|
                    dist_env.WhereIs("arm-none-eabi-gcc")
 | 
						|
                ),
 | 
						|
                "@UFBT_TOOLCHAIN_GDB_PY@": _path_as_posix(
 | 
						|
                    dist_env.WhereIs("arm-none-eabi-gdb-py")
 | 
						|
                ),
 | 
						|
                "@UFBT_TOOLCHAIN_OPENOCD@": _path_as_posix(dist_env.WhereIs("openocd")),
 | 
						|
                "@UFBT_APP_DIR@": _path_as_posix(original_app_dir.abspath),
 | 
						|
                "@UFBT_ROOT_DIR@": _path_as_posix(Dir("#").abspath),
 | 
						|
                "@UFBT_DEBUG_DIR@": dist_env["FBT_DEBUG_DIR"],
 | 
						|
                "@UFBT_DEBUG_ELF_DIR@": _path_as_posix(
 | 
						|
                    dist_env["FBT_FAP_DEBUG_ELF_ROOT"].abspath
 | 
						|
                ),
 | 
						|
                "@UFBT_FIRMWARE_ELF@": _path_as_posix(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)
 | 
						|
 | 
						|
 | 
						|
# 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',
 | 
						|
        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))
 | 
						|
 | 
						|
dist_env.PhonyTarget(
 | 
						|
    "env",
 | 
						|
    "@echo $( ${FBT_SCRIPT_DIR}/toolchain/fbtenv.sh $)",
 | 
						|
)
 | 
						|
 | 
						|
dist_env.PostConfigureUfbtEnvionment()
 |