~gnkv/flipperzero-firmware

309f65e40167799fb11a259b12ffa4e62338508b — hedger 10 months ago 9bb0483
[FL-3437] fbt: `build` target for faps (#2888)

* fbt: added build target for faps. Usage: ./fbt build APPSRC=<appid>
* Updated docs & vscode config
* Code cleanup
M .vscode/example/tasks.json => .vscode/example/tasks.json +16 -4
@@ 4,13 4,13 @@
    "version": "2.0.0",
    "tasks": [
        {
            "label": "[Release] Build",
            "label": "[Release] Build Firmware",
            "group": "build",
            "type": "shell",
            "command": "./fbt COMPACT=1 DEBUG=0"
        },
        {
            "label": "[Debug] Build",
            "label": "[Debug] Build Firmware",
            "group": "build",
            "type": "shell",
            "command": "./fbt"


@@ 124,16 124,28 @@
            "command": "./fbt COMPACT=1 DEBUG=0 fap_dist"
        },
        {
            "label": "[Debug] Build App",
            "group": "build",
            "type": "shell",
            "command": "./fbt build APPSRC=${relativeFileDirname}"
        },
        {
            "label": "[Release] Build App",
            "group": "build",
            "type": "shell",
            "command": "./fbt COMPACT=1 DEBUG=0 build APPSRC=${relativeFileDirname}"
        },
        {
            "label": "[Debug] Launch App on Flipper",
            "group": "build",
            "type": "shell",
            "command": "./fbt launch_app APPSRC=${relativeFileDirname}"
            "command": "./fbt launch APPSRC=${relativeFileDirname}"
        },
        {
            "label": "[Release] Launch App on Flipper",
            "group": "build",
            "type": "shell",
            "command": "./fbt COMPACT=1 DEBUG=0 launch_app APPSRC=${relativeFileDirname}"
            "command": "./fbt COMPACT=1 DEBUG=0 launch APPSRC=${relativeFileDirname}"
        },
        {
            "label": "[Debug] Launch App on Flipper with Serial Console",

M applications/examples/example_thermo/README.md => applications/examples/example_thermo/README.md +1 -1
@@ 18,7 18,7 @@ Before launching the application, connect the sensor to Flipper's external GPIO 
In order to launch this demo, follow the steps below:
1. Make sure your Flipper has an SD card installed.
2. Connect your Flipper to the computer via a USB cable.
3. Run `./fbt launch_app APPSRC=example_thermo` in your terminal emulator of choice.
3. Run `./fbt launch APPSRC=example_thermo` in your terminal emulator of choice.

## Changing the data pin
It is possible to use other GPIO pin as a 1-Wire data pin. In order to change it, set the `THERMO_GPIO_PIN` macro to any of the options listed below:

M documentation/AppsOnSDCard.md => documentation/AppsOnSDCard.md +2 -1
@@ 13,7 13,8 @@ FAPs are created and developed the same way as internal applications that are pa
To build your application as a FAP, create a folder with your app's source code in `applications_user`, then write its code the way you'd do when creating a regular built-in application. Then configure its `application.fam` manifest, and set its _apptype_ to FlipperAppType.EXTERNAL. See [Application Manifests](./AppManifests.md#application-definition) for more details.

- To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest.
- To build your app and upload it over USB to run on Flipper, use `./fbt launch_app APPSRC=applications_user/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu).
- To build your app and upload it over USB to run on Flipper, use `./fbt launch APPSRC=applications_user/path/to/app`. This command is configured in the default [VS Code profile](../.vscode/ReadMe.md) as a "Launch App on Flipper" build action (Ctrl+Shift+B menu).
- To build an app without uploading it to Flipper, use `./fbt build APPSRC=applications_user/path/to/app`. This command is also availabe in VSCode configuration as "Build App".
- To build all FAPs, run `./fbt faps` or `./fbt fap_dist`.

## FAP assets

M scripts/fbt/appmanifest.py => scripts/fbt/appmanifest.py +1 -4
@@ 328,10 328,7 @@ class AppBuildset:
            sdk_headers.extend(
                [
                    src._appdir.File(header)
                    for src in [
                        app,
                        *(plugin for plugin in app._plugins),
                    ]
                    for src in [app, *app._plugins]
                    for header in src.sdk_headers
                ]
            )

M scripts/fbt_tools/fbt_extapps.py => scripts/fbt_tools/fbt_extapps.py +35 -20
@@ 3,7 3,7 @@ import os
import pathlib
import shutil
from dataclasses import dataclass, field
from typing import Optional
from typing import Optional, Dict, List

import SCons.Warnings
from ansi.color import fg


@@ 400,22 400,26 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature):
    return Action(actions)


def AddAppLaunchTarget(env, appname, launch_target_name):
    deploy_sources, flipp_dist_paths, validators = [], [], []
    run_script_extra_ars = ""
@dataclass
class AppDeploymentComponents:
    deploy_sources: Dict[str, object] = field(default_factory=dict)
    validators: List[object] = field(default_factory=list)
    extra_launch_args: str = ""

    def _add_dist_targets(app_artifacts):
        validators.append(app_artifacts.validator)
    def add_app(self, app_artifacts):
        for _, ext_path in app_artifacts.dist_entries:
            deploy_sources.append(app_artifacts.compact)
            flipp_dist_paths.append(f"/ext/{ext_path}")
        return app_artifacts
            self.deploy_sources[f"/ext/{ext_path}"] = app_artifacts.compact
        self.validators.append(app_artifacts.validator)


def _gather_app_components(env, appname) -> AppDeploymentComponents:
    components = AppDeploymentComponents()

    def _add_host_app_to_targets(host_app):
        artifacts_app_to_run = env["EXT_APPS"].get(host_app.appid, None)
        _add_dist_targets(artifacts_app_to_run)
        components.add_app(artifacts_app_to_run)
        for plugin in host_app._plugins:
            _add_dist_targets(env["EXT_APPS"].get(plugin.appid, None))
            components.add_app(env["EXT_APPS"].get(plugin.appid, None))

    artifacts_app_to_run = env.GetExtAppByIdOrPath(appname)
    if artifacts_app_to_run.app.apptype == FlipperAppType.PLUGIN:


@@ 427,25 431,35 @@ def AddAppLaunchTarget(env, appname, launch_target_name):
                FlipperAppType.EXTERNAL,
                FlipperAppType.MENUEXTERNAL,
            ]:
                _add_host_app_to_targets(host_app)
                components.add_app(host_app)
            else:
                # host app is a built-in app
                run_script_extra_ars = f"-a {host_app.name}"
                _add_dist_targets(artifacts_app_to_run)
                components.add_app(artifacts_app_to_run)
                components.extra_launch_args = f"-a {host_app.name}"
        else:
            raise UserError("Host app is unknown")
    else:
        _add_host_app_to_targets(artifacts_app_to_run.app)
    return components

    # print(deploy_sources, flipp_dist_paths)
    env.PhonyTarget(

def AddAppLaunchTarget(env, appname, launch_target_name):
    components = _gather_app_components(env, appname)
    target = env.PhonyTarget(
        launch_target_name,
        '${PYTHON3} "${APP_RUN_SCRIPT}" -p ${FLIP_PORT} ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}',
        source=deploy_sources,
        FLIPPER_FILE_TARGETS=flipp_dist_paths,
        EXTRA_ARGS=run_script_extra_ars,
        source=components.deploy_sources.values(),
        FLIPPER_FILE_TARGETS=components.deploy_sources.keys(),
        EXTRA_ARGS=components.extra_launch_args,
    )
    env.Alias(launch_target_name, validators)
    env.Alias(launch_target_name, components.validators)
    return target


def AddAppBuildTarget(env, appname, build_target_name):
    components = _gather_app_components(env, appname)
    env.Alias(build_target_name, components.validators)
    env.Alias(build_target_name, components.deploy_sources.values())


def generate(env, **kw):


@@ 474,6 488,7 @@ def generate(env, **kw):
    env.AddMethod(BuildAppElf)
    env.AddMethod(GetExtAppByIdOrPath)
    env.AddMethod(AddAppLaunchTarget)
    env.AddMethod(AddAppBuildTarget)

    env.Append(
        BUILDERS={

M scripts/fbt_tools/fbt_help.py => scripts/fbt_tools/fbt_help.py +3 -2
@@ 4,15 4,16 @@ targets_help = """Configuration variables:
tail_help = """

TASKS:
Building:
Firmware & apps:
    firmware_all, fw_dist:
        Build firmware; create distribution package
    faps, fap_dist:
        Build all FAP apps
    fap_{APPID}, launch_app APPSRC={APPID}:
    fap_{APPID}, build APPSRC={APPID}; launch APPSRC={APPID}:
        Build FAP app with appid={APPID}; upload & start it over USB
    fap_deploy:
        Build and upload all FAP apps over USB
    

Flashing & debugging:
    flash, flash_blackmagic, jflash:

M scripts/ufbt/SConstruct => scripts/ufbt/SConstruct +2 -0
@@ 336,8 336,10 @@ def ambiguous_app_call(**kw):

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


M site_scons/extapps.scons => site_scons/extapps.scons +13 -1
@@ 114,8 114,20 @@ extapps.resources_dist = appenv.FapDist(appenv["RESOURCES_ROOT"], [])


if appsrc := appenv.subst("$APPSRC"):
    appenv.AddAppLaunchTarget(appsrc, "launch_app")
    launch_target = appenv.AddAppLaunchTarget(appsrc, "launch")
    Alias("launch_app", launch_target)
    appenv.PhonyTarget(
        "launch_app",
        Action(
            lambda **kw: warn(
                WarningOnByDefault,
                "The 'launch_app' target is deprecated. Use 'launch' instead.",
            ),
            None,
        ),
    )

    appenv.AddAppBuildTarget(appsrc, "build")

# SDK management