[FL-2832] fbt: more fixes & improvements (#1854)

* github: bundling debug folder with scripts; docs: fixes & updates; fbt: added FAP_EXAMPLES variable to enable building example apps. Disabled by default. fbt: added TERM to list of proxied environment variables
* fbt: better help output; disabled implicit_deps_unchanged; added color to import validator reports
* fbt: moved debug configuration to separate tool
* fbt: proper dependency tracker for SDK source file; renamed linker script for external apps
* fbt: fixed debug elf path
* fbt: packaging sdk archive
* scripts: fixed sconsdist.py
* fbt: reworked sdk packing; docs: updates
* docs: info on cli target; linter fixes
* fbt: moved main code to scripts folder
* scripts: packing update into .tgz
* fbt, scripts: reworked copro_dist to build .tgz
* scripts: fixed naming for archived updater package
* Scripts: fix ぐるぐる回る

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
hedger 2022-10-12 20:12:05 +04:00 committed by GitHub
parent afff1adf8f
commit eb4ff3c0fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 413 additions and 272 deletions

View File

@ -56,14 +56,14 @@ jobs:
- name: 'Bundle scripts' - name: 'Bundle scripts'
if: ${{ !github.event.pull_request.head.repo.fork }} if: ${{ !github.event.pull_request.head.repo.fork }}
run: | run: |
tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts tar czpf artifacts/flipper-z-any-scripts-${SUFFIX}.tgz scripts debug
- name: 'Build the firmware' - name: 'Build the firmware'
run: | run: |
set -e set -e
for TARGET in ${TARGETS}; do for TARGET in ${TARGETS}; do
FBT_TOOLCHAIN_PATH=/runner/_work ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \ FBT_TOOLCHAIN_PATH=/runner/_work ./fbt TARGET_HW="$(echo "${TARGET}" | sed 's/f//')" \
updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }} copro_dist updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }}
done done
- name: 'Move upload files' - name: 'Move upload files'
@ -74,17 +74,6 @@ jobs:
mv dist/${TARGET}-*/* artifacts/ mv dist/${TARGET}-*/* artifacts/
done done
- name: 'Bundle self-update package'
if: ${{ !github.event.pull_request.head.repo.fork }}
run: |
set -e
for UPDATEBUNDLE in artifacts/*/; do
BUNDLE_NAME="$(echo "$UPDATEBUNDLE" | cut -d'/' -f2)"
echo Packaging "${BUNDLE_NAME}"
tar czpf "artifacts/flipper-z-${BUNDLE_NAME}.tgz" -C artifacts "${BUNDLE_NAME}"
rm -rf "artifacts/${BUNDLE_NAME}"
done
- name: "Check for uncommitted changes" - name: "Check for uncommitted changes"
run: | run: |
git diff --exit-code git diff --exit-code
@ -97,8 +86,7 @@ jobs:
- name: 'Bundle core2 firmware' - name: 'Bundle core2 firmware'
if: ${{ !github.event.pull_request.head.repo.fork }} if: ${{ !github.event.pull_request.head.repo.fork }}
run: | run: |
FBT_TOOLCHAIN_PATH=/runner/_work ./fbt copro_dist cp build/core2_firmware.tgz "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz"
tar czpf "artifacts/flipper-z-any-core2_firmware-${SUFFIX}.tgz" -C assets core2_firmware
- name: 'Copy .map file' - name: 'Copy .map file'
run: | run: |

View File

@ -7,7 +7,6 @@
# construction of certain targets behind command-line options. # construction of certain targets behind command-line options.
import os import os
import subprocess
DefaultEnvironment(tools=[]) DefaultEnvironment(tools=[])
@ -15,17 +14,22 @@ EnsurePythonVersion(3, 8)
# Progress(["OwO\r", "owo\r", "uwu\r", "owo\r"], interval=15) # Progress(["OwO\r", "owo\r", "uwu\r", "owo\r"], interval=15)
# This environment is created only for loading options & validating file/dir existence # This environment is created only for loading options & validating file/dir existence
fbt_variables = SConscript("site_scons/commandline.scons") fbt_variables = SConscript("site_scons/commandline.scons")
cmd_environment = Environment(tools=[], variables=fbt_variables) cmd_environment = Environment(
Help(fbt_variables.GenerateHelpText(cmd_environment)) toolpath=["#/scripts/fbt_tools"],
tools=[
("fbt_help", {"vars": fbt_variables}),
],
variables=fbt_variables,
)
# Building basic environment - tools, utility methods, cross-compilation # Building basic environment - tools, utility methods, cross-compilation
# settings, gcc flags for Cortex-M4, basic builders and more # settings, gcc flags for Cortex-M4, basic builders and more
coreenv = SConscript( coreenv = SConscript(
"site_scons/environ.scons", "site_scons/environ.scons",
exports={"VAR_ENV": cmd_environment}, exports={"VAR_ENV": cmd_environment},
toolpath=["#/scripts/fbt_tools"],
) )
SConscript("site_scons/cc.scons", exports={"ENV": coreenv}) SConscript("site_scons/cc.scons", exports={"ENV": coreenv})
@ -35,41 +39,13 @@ coreenv["ROOT_DIR"] = Dir(".")
# Create a separate "dist" environment and add construction envs to it # Create a separate "dist" environment and add construction envs to it
distenv = coreenv.Clone( distenv = coreenv.Clone(
tools=["fbt_dist", "openocd", "blackmagic", "jflash"], tools=[
OPENOCD_GDB_PIPE=[ "fbt_dist",
"|openocd -c 'gdb_port pipe; log_output debug/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}" "fbt_debugopts",
"openocd",
"blackmagic",
"jflash",
], ],
GDBOPTS_BASE=[
"-ex",
"target extended-remote ${GDBREMOTE}",
"-ex",
"set confirm off",
"-ex",
"set pagination off",
],
GDBOPTS_BLACKMAGIC=[
"-ex",
"monitor swdp_scan",
"-ex",
"monitor debug_bmp enable",
"-ex",
"attach 1",
"-ex",
"set mem inaccessible-by-default off",
],
GDBPYOPTS=[
"-ex",
"source debug/FreeRTOS/FreeRTOS.py",
"-ex",
"source debug/flipperapps.py",
"-ex",
"source debug/PyCortexMDebug/PyCortexMDebug.py",
"-ex",
"svd_load ${SVD_FILE}",
"-ex",
"compare-sections",
],
JFLASHPROJECT="${ROOT_DIR.abspath}/debug/fw.jflash",
ENV=os.environ, ENV=os.environ,
) )
@ -166,7 +142,7 @@ basic_dist = distenv.DistCommand("fw_dist", distenv["DIST_DEPENDS"])
distenv.Default(basic_dist) distenv.Default(basic_dist)
dist_dir = distenv.GetProjetDirName() dist_dir = distenv.GetProjetDirName()
plugin_dist = [ fap_dist = [
distenv.Install( distenv.Install(
f"#/dist/{dist_dir}/apps/debug_elf", f"#/dist/{dist_dir}/apps/debug_elf",
firmware_env["FW_EXTAPPS"]["debug"].values(), firmware_env["FW_EXTAPPS"]["debug"].values(),
@ -176,9 +152,9 @@ plugin_dist = [
for dist_entry in firmware_env["FW_EXTAPPS"]["dist"].values() for dist_entry in firmware_env["FW_EXTAPPS"]["dist"].values()
), ),
] ]
Depends(plugin_dist, firmware_env["FW_EXTAPPS"]["validators"].values()) Depends(fap_dist, firmware_env["FW_EXTAPPS"]["validators"].values())
Alias("plugin_dist", plugin_dist) Alias("fap_dist", fap_dist)
# distenv.Default(plugin_dist) # distenv.Default(fap_dist)
plugin_resources_dist = list( plugin_resources_dist = list(
distenv.Install(f"#/assets/resources/apps/{dist_entry[0]}", dist_entry[1]) distenv.Install(f"#/assets/resources/apps/{dist_entry[0]}", dist_entry[1])
@ -189,9 +165,10 @@ distenv.Depends(firmware_env["FW_RESOURCES"], plugin_resources_dist)
# Target for bundling core2 package for qFlipper # Target for bundling core2 package for qFlipper
copro_dist = distenv.CoproBuilder( copro_dist = distenv.CoproBuilder(
distenv.Dir("assets/core2_firmware"), "#/build/core2_firmware.tgz",
[], [],
) )
distenv.AlwaysBuild(copro_dist)
distenv.Alias("copro_dist", copro_dist) distenv.Alias("copro_dist", copro_dist)
firmware_flash = distenv.AddOpenOCDFlashTarget(firmware_env) firmware_flash = distenv.AddOpenOCDFlashTarget(firmware_env)

View File

@ -4,6 +4,8 @@
#include <gui/gui.h> #include <gui/gui.h>
#include <input/input.h> #include <input/input.h>
/* Magic happens here -- this file is generated by fbt.
* Just set fap_icon_assets in application.fam and #include {APPID}_icons.h */
#include "example_images_icons.h" #include "example_images_icons.h"
typedef struct { typedef struct {

View File

@ -30,7 +30,7 @@ Only 2 parameters are mandatory: ***appid*** and ***apptype***, others are optio
| METAPACKAGE | Does not define any code to be run, used for declaring dependencies and application bundles | | METAPACKAGE | Does not define any code to be run, used for declaring dependencies and application bundles |
* **name**: Name that is displayed in menus. * **name**: Name that is displayed in menus.
* **entry_point**: C function to be used as application's entry point. * **entry_point**: C function to be used as application's entry point. Note that C++ function names are mangled, so you need to wrap them in `extern "C"` in order to use them as entry points.
* **flags**: Internal flags for system apps. Do not use. * **flags**: Internal flags for system apps. Do not use.
* **cdefines**: C preprocessor definitions to declare globally for other apps when current application is included in active build configuration. * **cdefines**: C preprocessor definitions to declare globally for other apps when current application is included in active build configuration.
* **requires**: List of application IDs to also include in build configuration, when current application is referenced in list of applications to build. * **requires**: List of application IDs to also include in build configuration, when current application is referenced in list of applications to build.
@ -55,7 +55,7 @@ The following parameters are used only for [FAPs](./AppsOnSDCard.md):
* **fap_author**: string, may be empty. Application's author. * **fap_author**: string, may be empty. Application's author.
* **fap_weburl**: string, may be empty. Application's homepage. * **fap_weburl**: string, may be empty. Application's homepage.
* **fap_icon_assets**: string. If present, defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details. * **fap_icon_assets**: string. If present, defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details.
* **fap_extbuild**: provides support for parts of application sources to be build by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. * **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list.
Note that commands are executed at the firmware root folder's root, and all intermediate files must be placed in a application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**. Note that commands are executed at the firmware root folder's root, and all intermediate files must be placed in a application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**.
Example for building an app from Rust sources: Example for building an app from Rust sources:

View File

@ -2,7 +2,7 @@
[fbt](./fbt.md) has support for building applications as FAP files. FAP are essentially .elf executables with extra metadata and resources bundled in. [fbt](./fbt.md) has support for building applications as FAP files. FAP are essentially .elf executables with extra metadata and resources bundled in.
FAPs are built with `faps` **`fbt`** target. They can also be deployed to `dist` folder with `plugin_dist` **`fbt`** target. FAPs are built with `faps` target. They can also be deployed to `dist` folder with `fap_dist` target.
FAPs do not depend on being run on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning). FAPs do not depend on being run on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning).
@ -15,7 +15,7 @@ To build your application as a FAP, just create a folder with your app's source
* To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest. * To build your application, run `./fbt fap_{APPID}`, where APPID is your application's ID in its manifest.
* To build your app, then upload it over USB & run it on Flipper, use `./fbt launch_app APPSRC=applications/path/to/app`. This command is configured in default [VSCode profile](../.vscode/ReadMe.md) as "Launch App on Flipper" build action (Ctrl+Shift+B menu). * To build your app, then upload it over USB & run it on Flipper, use `./fbt launch_app APPSRC=applications/path/to/app`. This command is configured in default [VSCode profile](../.vscode/ReadMe.md) as "Launch App on Flipper" build action (Ctrl+Shift+B menu).
* To build all FAPs, run `./fbt plugin_dist`. * To build all FAPs, run `./fbt faps` or `./fbt fap_dist`.
## FAP assets ## FAP assets

View File

@ -43,7 +43,7 @@ To run cleanup (think of `make clean`) for specified targets, add `-c` option.
### High-level (what you most likely need) ### High-level (what you most likely need)
- `fw_dist` - build & publish firmware to `dist` folder. This is a default target, when no other are specified - `fw_dist` - build & publish firmware to `dist` folder. This is a default target, when no other are specified
- `plugin_dist` - build external plugins & publish to `dist` folder - `fap_dist` - build external plugins & publish to `dist` folder
- `updater_package`, `updater_minpackage` - build self-update package. Minimal version only inclues firmware's DFU file; full version also includes radio stack & resources for SD card - `updater_package`, `updater_minpackage` - build self-update package. Minimal version only inclues firmware's DFU file; full version also includes radio stack & resources for SD card
- `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper - `copro_dist` - bundle Core2 FUS+stack binaries for qFlipper
- `flash` - flash attached device with OpenOCD over ST-Link - `flash` - flash attached device with OpenOCD over ST-Link
@ -56,6 +56,7 @@ To run cleanup (think of `make clean`) for specified targets, add `-c` option.
- `get_blackmagic` - output blackmagic address in gdb remote format. Useful for IDE integration - `get_blackmagic` - output blackmagic address in gdb remote format. Useful for IDE integration
- `lint`, `format` - run clang-format on C source code to check and reformat it according to `.clang-format` specs - `lint`, `format` - run clang-format on C source code to check and reformat it according to `.clang-format` specs
- `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on Python source code, build system files & application manifests - `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on Python source code, build system files & application manifests
- `cli` - start Flipper CLI session over USB
### Firmware targets ### Firmware targets

View File

@ -3,7 +3,7 @@ Import("ENV", "fw_build_meta")
from SCons.Errors import UserError from SCons.Errors import UserError
import itertools import itertools
from fbt.util import ( from fbt_extra.util import (
should_gen_cdb_and_link_dir, should_gen_cdb_and_link_dir,
link_elf_dir_as_latest, link_elf_dir_as_latest,
) )
@ -141,6 +141,10 @@ else:
if extra_int_apps := GetOption("extra_int_apps"): if extra_int_apps := GetOption("extra_int_apps"):
fwenv.Append(APPS=extra_int_apps.split(",")) fwenv.Append(APPS=extra_int_apps.split(","))
if fwenv["FAP_EXAMPLES"]:
fwenv.Append(APPDIRS=[("applications/examples", False)])
fwenv.LoadApplicationManifests() fwenv.LoadApplicationManifests()
fwenv.PrepareApplicationsBuild() fwenv.PrepareApplicationsBuild()
@ -316,10 +320,13 @@ if fwenv["IS_BASE_FIRMWARE"]:
"-D__inline__=inline", "-D__inline__=inline",
], ],
) )
Depends(sdk_source, (fwenv["SDK_HEADERS"], fwenv["FW_ASSETS_HEADERS"])) # Depends(sdk_source, (fwenv["SDK_HEADERS"], fwenv["FW_ASSETS_HEADERS"]))
Depends(sdk_source, fwenv.ProcessSdkDepends("sdk_origin.d"))
sdk_tree = fwenv.SDKTree("sdk/sdk.opts", "sdk_origin") fwenv["SDK_DIR"] = fwenv.Dir("sdk")
AlwaysBuild(sdk_tree) sdk_tree = fwenv.SDKTree(fwenv["SDK_DIR"], "sdk_origin")
fw_artifacts.append(sdk_tree)
# AlwaysBuild(sdk_tree)
Alias("sdk_tree", sdk_tree) Alias("sdk_tree", sdk_tree)
sdk_apicheck = fwenv.SDKSymUpdater(fwenv.subst("$SDK_DEFINITION"), "sdk_origin") sdk_apicheck = fwenv.SDKSymUpdater(fwenv.subst("$SDK_DEFINITION"), "sdk_origin")
@ -329,7 +336,7 @@ if fwenv["IS_BASE_FIRMWARE"]:
Alias("sdk_check", sdk_apicheck) Alias("sdk_check", sdk_apicheck)
sdk_apisyms = fwenv.SDKSymGenerator( sdk_apisyms = fwenv.SDKSymGenerator(
"assets/compiled/symbols.h", fwenv.subst("$SDK_DEFINITION") "assets/compiled/symbols.h", fwenv["SDK_DEFINITION"]
) )
Alias("api_syms", sdk_apisyms) Alias("api_syms", sdk_apisyms)

View File

@ -41,25 +41,3 @@ def link_dir(target_path, source_path, is_windows):
def single_quote(arg_list): def single_quote(arg_list):
return " ".join(f"'{arg}'" if " " in arg else str(arg) for arg in arg_list) return " ".join(f"'{arg}'" if " " in arg else str(arg) for arg in arg_list)
def link_elf_dir_as_latest(env, elf_node):
elf_dir = elf_node.Dir(".")
latest_dir = env.Dir("#build/latest")
print(f"Setting {elf_dir} as latest built dir (./build/latest/)")
return link_dir(latest_dir.abspath, elf_dir.abspath, env["PLATFORM"] == "win32")
def should_gen_cdb_and_link_dir(env, requested_targets):
explicitly_building_updater = False
# Hacky way to check if updater-related targets were requested
for build_target in requested_targets:
if "updater" in str(build_target):
explicitly_building_updater = True
is_updater = not env["IS_BASE_FIRMWARE"]
# If updater is explicitly requested, link to the latest updater
# Otherwise, link to firmware
return (is_updater and explicitly_building_updater) or (
not is_updater and not explicitly_building_updater
)

View File

@ -0,0 +1,41 @@
def generate(env, **kw):
env.SetDefault(
OPENOCD_GDB_PIPE=[
"|openocd -c 'gdb_port pipe; log_output debug/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}"
],
GDBOPTS_BASE=[
"-ex",
"target extended-remote ${GDBREMOTE}",
"-ex",
"set confirm off",
"-ex",
"set pagination off",
],
GDBOPTS_BLACKMAGIC=[
"-ex",
"monitor swdp_scan",
"-ex",
"monitor debug_bmp enable",
"-ex",
"attach 1",
"-ex",
"set mem inaccessible-by-default off",
],
GDBPYOPTS=[
"-ex",
"source debug/FreeRTOS/FreeRTOS.py",
"-ex",
"source debug/flipperapps.py",
"-ex",
"source debug/PyCortexMDebug/PyCortexMDebug.py",
"-ex",
"svd_load ${SVD_FILE}",
"-ex",
"compare-sections",
],
JFLASHPROJECT="${ROOT_DIR.abspath}/debug/fw.jflash",
)
def exists(env):
return True

View File

@ -136,7 +136,6 @@ def generate(env):
"CoproBuilder": Builder( "CoproBuilder": Builder(
action=Action( action=Action(
[ [
Mkdir("$TARGET"),
'${PYTHON3} "${ROOT_DIR.abspath}/scripts/assets.py" ' '${PYTHON3} "${ROOT_DIR.abspath}/scripts/assets.py" '
"copro ${COPRO_CUBE_DIR} " "copro ${COPRO_CUBE_DIR} "
"${TARGET} ${COPRO_MCU_FAMILY} " "${TARGET} ${COPRO_MCU_FAMILY} "
@ -145,7 +144,7 @@ def generate(env):
'--stack_file="${COPRO_STACK_BIN}" ' '--stack_file="${COPRO_STACK_BIN}" '
"--stack_addr=${COPRO_STACK_ADDR} ", "--stack_addr=${COPRO_STACK_ADDR} ",
], ],
"", "\tCOPRO\t${TARGET}",
) )
), ),
} }

View File

@ -6,12 +6,10 @@ import SCons.Warnings
import os import os
import pathlib import pathlib
from fbt.elfmanifest import assemble_manifest_data from fbt.elfmanifest import assemble_manifest_data
from fbt.appmanifest import FlipperManifestException from fbt.appmanifest import FlipperApplication, FlipperManifestException
from fbt.sdk import SdkCache from fbt.sdk import SdkCache
import itertools import itertools
from site_scons.fbt.appmanifest import FlipperApplication
def BuildAppElf(env, app): def BuildAppElf(env, app):
ext_apps_work_dir = env.subst("$EXT_APPS_WORK_DIR") ext_apps_work_dir = env.subst("$EXT_APPS_WORK_DIR")
@ -111,7 +109,7 @@ def BuildAppElf(env, app):
) )
app_elf_raw = app_env.Program( app_elf_raw = app_env.Program(
os.path.join(app_work_dir, f"{app.appid}_d"), os.path.join(ext_apps_work_dir, f"{app.appid}_d"),
app_sources, app_sources,
APP_ENTRY=app.entry_point, APP_ENTRY=app.entry_point,
) )
@ -180,7 +178,7 @@ def validate_app_imports(target, source, env):
if unresolved_syms: if unresolved_syms:
SCons.Warnings.warn( SCons.Warnings.warn(
SCons.Warnings.LinkWarning, SCons.Warnings.LinkWarning,
f"{source[0].path}: app won't run. Unresolved symbols: {unresolved_syms}", f"\033[93m{source[0].path}: app won't run. Unresolved symbols: \033[95m{unresolved_syms}\033[0m",
) )

View File

@ -0,0 +1,44 @@
targets_help = """Configuration variables:
"""
tail_help = """
TASKS:
Building:
firmware_all, fw_dist:
Build firmware; create distribution package
faps, fap_dist:
Build all FAP apps
fap_{APPID}, launch_app APPSRC={APPID}:
Build FAP app with appid={APPID}; upload & start it over USB
Flashing & debugging:
flash, flash_blackmagic, jflash:
Flash firmware to target using debug probe
flash_usb, flash_usb_full:
Install firmware using self-update package
debug, debug_other, blackmagic:
Start GDB
Other:
cli:
Open a Flipper CLI session over USB
firmware_cdb, updater_cdb:
Generate сompilation_database.json
lint, lint_py:
run linters
format, format_py:
run code formatters
For more targets & info, see documentation/fbt.md
"""
def generate(env, **kw):
vars = kw["vars"]
basic_help = vars.GenerateHelpText(env)
env.Help(targets_help + basic_help + tail_help)
def exists(env):
return True

View File

@ -9,10 +9,32 @@ from SCons.Util import LogicalLines
import os.path import os.path
import posixpath import posixpath
import pathlib import pathlib
import json
from fbt.sdk import SdkCollector, SdkCache from fbt.sdk import SdkCollector, SdkCache
def ProcessSdkDepends(env, filename):
try:
with open(filename, "r") as fin:
lines = LogicalLines(fin).readlines()
except IOError:
return []
_, depends = lines[0].split(":", 1)
depends = depends.split()
depends.pop(0) # remove the .c file
depends = list(
# Don't create dependency on non-existing files
# (e.g. when they were renamed since last build)
filter(
lambda file: file.exists(),
(env.File(f"#{path}") for path in depends),
)
)
return depends
def prebuild_sdk_emitter(target, source, env): def prebuild_sdk_emitter(target, source, env):
target.append(env.ChangeFileExtension(target[0], ".d")) target.append(env.ChangeFileExtension(target[0], ".d"))
target.append(env.ChangeFileExtension(target[0], ".i.c")) target.append(env.ChangeFileExtension(target[0], ".i.c"))
@ -25,6 +47,25 @@ def prebuild_sdk_create_origin_file(target, source, env):
sdk_c.write("\n".join(f"#include <{h.path}>" for h in env["SDK_HEADERS"])) sdk_c.write("\n".join(f"#include <{h.path}>" for h in env["SDK_HEADERS"]))
class SdkMeta:
def __init__(self, env):
self.env = env
def save_to(self, json_manifest_path: str):
meta_contents = {
"sdk_symbols": self.env["SDK_DEFINITION"].name,
"cc_args": self._wrap_scons_vars("$CCFLAGS $_CCCOMCOM"),
"cpp_args": self._wrap_scons_vars("$CXXFLAGS $CCFLAGS $_CCCOMCOM"),
"linker_args": self._wrap_scons_vars("$LINKFLAGS"),
}
with open(json_manifest_path, "wt") as f:
json.dump(meta_contents, f, indent=4)
def _wrap_scons_vars(self, vars: str):
expanded_vars = self.env.subst(vars, target=Entry("dummy"))
return expanded_vars.replace("\\", "/")
class SdkTreeBuilder: class SdkTreeBuilder:
def __init__(self, env, target, source) -> None: def __init__(self, env, target, source) -> None:
self.env = env self.env = env
@ -34,8 +75,9 @@ class SdkTreeBuilder:
self.header_depends = [] self.header_depends = []
self.header_dirs = [] self.header_dirs = []
self.target_sdk_dir = env.subst("f${TARGET_HW}_sdk") self.target_sdk_dir_name = env.subst("f${TARGET_HW}_sdk")
self.sdk_deploy_dir = target[0].Dir(self.target_sdk_dir) self.sdk_root_dir = target[0].Dir(".")
self.sdk_deploy_dir = self.sdk_root_dir.Dir(self.target_sdk_dir_name)
def _parse_sdk_depends(self): def _parse_sdk_depends(self):
deps_file = self.source[0] deps_file = self.source[0]
@ -50,7 +92,7 @@ class SdkTreeBuilder:
) )
def _generate_sdk_meta(self): def _generate_sdk_meta(self):
filtered_paths = [self.target_sdk_dir] filtered_paths = [self.target_sdk_dir_name]
full_fw_paths = list( full_fw_paths = list(
map( map(
os.path.normpath, os.path.normpath,
@ -62,17 +104,18 @@ class SdkTreeBuilder:
for dir in full_fw_paths: for dir in full_fw_paths:
if dir in sdk_dirs: if dir in sdk_dirs:
filtered_paths.append( filtered_paths.append(
posixpath.normpath(posixpath.join(self.target_sdk_dir, dir)) posixpath.normpath(posixpath.join(self.target_sdk_dir_name, dir))
) )
sdk_env = self.env.Clone() sdk_env = self.env.Clone()
sdk_env.Replace(CPPPATH=filtered_paths) sdk_env.Replace(CPPPATH=filtered_paths)
with open(self.target[0].path, "wt") as f: meta = SdkMeta(sdk_env)
cmdline_options = sdk_env.subst( meta.save_to(self.target[0].path)
"$CCFLAGS $_CCCOMCOM", target=Entry("dummy")
) def emitter(self, target, source, env):
f.write(cmdline_options.replace("\\", "/")) target_folder = target[0]
f.write("\n") target = [target_folder.File("sdk.opts")]
return target, source
def _create_deploy_commands(self): def _create_deploy_commands(self):
dirs_to_create = set( dirs_to_create = set(
@ -81,13 +124,17 @@ class SdkTreeBuilder:
actions = [ actions = [
Delete(self.sdk_deploy_dir), Delete(self.sdk_deploy_dir),
Mkdir(self.sdk_deploy_dir), Mkdir(self.sdk_deploy_dir),
Copy(
self.sdk_root_dir,
self.env["SDK_DEFINITION"],
),
] ]
actions += [Mkdir(d) for d in dirs_to_create] actions += [Mkdir(d) for d in dirs_to_create]
actions += [ actions += [
Copy( Action(
self.sdk_deploy_dir.File(h).path, Copy(self.sdk_deploy_dir.File(h).path, h),
h, # f"Copy {h} to {self.sdk_deploy_dir}",
) )
for h in self.header_depends for h in self.header_depends
] ]
@ -108,6 +155,11 @@ def deploy_sdk_tree(target, source, env, for_signature):
return sdk_tree.generate_actions() return sdk_tree.generate_actions()
def deploy_sdk_tree_emitter(target, source, env):
sdk_tree = SdkTreeBuilder(env, target, source)
return sdk_tree.emitter(target, source, env)
def gen_sdk_data(sdk_cache: SdkCache): def gen_sdk_data(sdk_cache: SdkCache):
api_def = [] api_def = []
api_def.extend( api_def.extend(
@ -165,6 +217,7 @@ def generate_sdk_symbols(source, target, env):
def generate(env, **kw): def generate(env, **kw):
env.AddMethod(ProcessSdkDepends)
env.Append( env.Append(
BUILDERS={ BUILDERS={
"SDKPrebuilder": Builder( "SDKPrebuilder": Builder(
@ -183,6 +236,7 @@ def generate(env, **kw):
), ),
"SDKTree": Builder( "SDKTree": Builder(
generator=deploy_sdk_tree, generator=deploy_sdk_tree,
emitter=deploy_sdk_tree_emitter,
src_suffix=".d", src_suffix=".d",
), ),
"SDKSymUpdater": Builder( "SDKSymUpdater": Builder(

View File

@ -1,10 +1,9 @@
import logging import logging
import datetime
import shutil
import json import json
from os.path import basename from io import BytesIO
import tarfile
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from flipper.utils import * from flipper.utils import *
from flipper.assets.coprobin import CoproBinary, get_stack_type from flipper.assets.coprobin import CoproBinary, get_stack_type
@ -51,20 +50,19 @@ class Copro:
raise Exception(f"Unsupported cube version") raise Exception(f"Unsupported cube version")
self.version = cube_version self.version = cube_version
@staticmethod
def _getFileName(name):
return os.path.join("core2_firmware", name)
def addFile(self, array, filename, **kwargs): def addFile(self, array, filename, **kwargs):
source_file = os.path.join(self.mcu_copro, filename) source_file = os.path.join(self.mcu_copro, filename)
destination_file = os.path.join(self.output_dir, filename) self.output_tar.add(source_file, arcname=self._getFileName(filename))
shutil.copyfile(source_file, destination_file) array.append({"name": filename, "sha256": file_sha256(source_file), **kwargs})
array.append(
{"name": filename, "sha256": file_sha256(destination_file), **kwargs} def bundle(self, output_file, stack_file_name, stack_type, stack_addr=None):
) self.output_tar = tarfile.open(output_file, "w:gz")
def bundle(self, output_dir, stack_file_name, stack_type, stack_addr=None):
if not os.path.isdir(output_dir):
raise Exception(f'"{output_dir}" doesn\'t exists')
self.output_dir = output_dir
stack_file = os.path.join(self.mcu_copro, stack_file_name) stack_file = os.path.join(self.mcu_copro, stack_file_name)
manifest_file = os.path.join(self.output_dir, "Manifest.json")
# Form Manifest # Form Manifest
manifest = dict(MANIFEST_TEMPLATE) manifest = dict(MANIFEST_TEMPLATE)
manifest["manifest"]["timestamp"] = timestamp() manifest["manifest"]["timestamp"] = timestamp()
@ -105,6 +103,10 @@ class Copro:
stack_file_name, stack_file_name,
address=f"0x{stack_addr:X}", address=f"0x{stack_addr:X}",
) )
# Save manifest to
with open(manifest_file, "w", newline="\n") as file: # Save manifest
json.dump(manifest, file) manifest_data = json.dumps(manifest, indent=4).encode("utf-8")
info = tarfile.TarInfo(self._getFileName("Manifest.json"))
info.size = len(manifest_data)
self.output_tar.addfile(info, BytesIO(manifest_data))
self.output_tar.close()

View File

@ -17,7 +17,7 @@ class Main(App):
async def rebuild(self, line): async def rebuild(self, line):
self.clearConsole() self.clearConsole()
self.logger.info(f"Triggered by: {line}") self.logger.info(f"Triggered by: {line}")
proc = await asyncio.create_subprocess_exec("make") proc = await asyncio.create_subprocess_exec("./fbt")
await proc.wait() await proc.wait()
await asyncio.sleep(1) await asyncio.sleep(1)
self.is_building = False self.is_building = False

View File

@ -1,10 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from flipper.app import App from flipper.app import App
from os.path import join, exists from os.path import join, exists, relpath
from os import makedirs from os import makedirs, walk
from update import Main as UpdateMain from update import Main as UpdateMain
import shutil import shutil
import zipfile
import tarfile
class ProjectDir: class ProjectDir:
@ -17,6 +19,8 @@ class ProjectDir:
class Main(App): class Main(App):
DIST_FILE_PREFIX = "flipper-z-"
def init(self): def init(self):
self.subparsers = self.parser.add_subparsers(help="sub-command help") self.subparsers = self.parser.add_subparsers(help="sub-command help")
@ -45,9 +49,13 @@ class Main(App):
def get_project_filename(self, project, filetype): def get_project_filename(self, project, filetype):
# Temporary fix # Temporary fix
project_name = project.project project_name = project.project
if project_name == "firmware" and filetype != "elf": if project_name == "firmware":
project_name = "full" if filetype == "zip":
return f"flipper-z-{self.target}-{project_name}-{self.args.suffix}.{filetype}" project_name = "sdk"
elif filetype != "elf":
project_name = "full"
return f"{self.DIST_FILE_PREFIX}{self.target}-{project_name}-{self.args.suffix}.{filetype}"
def get_dist_filepath(self, filename): def get_dist_filepath(self, filename):
return join(self.output_dir_path, filename) return join(self.output_dir_path, filename)
@ -56,10 +64,28 @@ class Main(App):
obj_directory = join("build", project.dir) obj_directory = join("build", project.dir)
for filetype in ("elf", "bin", "dfu", "json"): for filetype in ("elf", "bin", "dfu", "json"):
shutil.copyfile( if exists(src_file := join(obj_directory, f"{project.project}.{filetype}")):
join(obj_directory, f"{project.project}.{filetype}"), shutil.copyfile(
self.get_dist_filepath(self.get_project_filename(project, filetype)), src_file,
) self.get_dist_filepath(
self.get_project_filename(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): def copy(self):
self.projects = dict( self.projects = dict(
@ -103,9 +129,8 @@ class Main(App):
) )
if self.args.version: if self.args.version:
bundle_dir = join( bundle_dir_name = f"{self.target}-update-{self.args.suffix}"
self.output_dir_path, f"{self.target}-update-{self.args.suffix}" bundle_dir = join(self.output_dir_path, bundle_dir_name)
)
bundle_args = [ bundle_args = [
"generate", "generate",
"-d", "-d",
@ -131,10 +156,24 @@ class Main(App):
) )
) )
bundle_args.extend(self.other_args) bundle_args.extend(self.other_args)
self.logger.info(
f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0:
) self.logger.info(
return UpdateMain(no_exit=True)(bundle_args) f"Use this directory to self-update your Flipper:\n\t{bundle_dir}"
)
# Create tgz archive
with tarfile.open(
join(
self.output_dir_path,
f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz",
),
"w:gz",
compresslevel=9,
) as tar:
tar.add(bundle_dir, arcname=bundle_dir_name)
return bundle_result
return 0 return 0

View File

@ -81,46 +81,41 @@ vars.AddVariables(
help="Enable debug tools to be built", help="Enable debug tools to be built",
default=False, default=False,
), ),
) BoolVariable(
"FAP_EXAMPLES",
vars.Add( help="Enable example applications to be built",
"DIST_SUFFIX", default=False,
help="Suffix for binaries in build output for dist targets", ),
default="local", (
) "DIST_SUFFIX",
"Suffix for binaries in build output for dist targets",
vars.Add( "local",
"UPDATE_VERSION_STRING", ),
help="Version string for updater package", (
default="${DIST_SUFFIX}", "UPDATE_VERSION_STRING",
) "Version string for updater package",
"${DIST_SUFFIX}",
),
vars.Add( (
"COPRO_CUBE_VERSION", "COPRO_CUBE_VERSION",
help="Cube version", "Cube version",
default="", "",
) ),
(
vars.Add( "COPRO_STACK_ADDR",
"COPRO_STACK_ADDR", "Core2 Firmware address",
help="Core2 Firmware address", "0",
default="0", ),
) (
"COPRO_STACK_BIN",
vars.Add( "Core2 Firmware file name",
"COPRO_STACK_BIN", "",
help="Core2 Firmware file name", ),
default="", (
) "COPRO_DISCLAIMER",
"Value to pass to bundling script to confirm dangerous operations",
vars.Add( "",
"COPRO_DISCLAIMER", ),
help="Value to pass to bundling script to confirm dangerous operations",
default="",
)
vars.AddVariables(
PathVariable( PathVariable(
"COPRO_OB_DATA", "COPRO_OB_DATA",
help="Path to OB reference data", help="Path to OB reference data",
@ -161,86 +156,75 @@ vars.AddVariables(
validator=PathVariable.PathAccept, validator=PathVariable.PathAccept,
default="", default="",
), ),
) (
"FBT_TOOLCHAIN_VERSIONS",
vars.Add( "Whitelisted toolchain versions (leave empty for no check)",
"FBT_TOOLCHAIN_VERSIONS", tuple(),
help="Whitelisted toolchain versions (leave empty for no check)", ),
default=tuple(), (
) "OPENOCD_OPTS",
"Options to pass to OpenOCD",
vars.Add( "",
"OPENOCD_OPTS", ),
help="Options to pass to OpenOCD", (
default="", "BLACKMAGIC",
) "Blackmagic probe location",
"auto",
vars.Add( ),
"BLACKMAGIC", (
help="Blackmagic probe location", "UPDATE_SPLASH",
default="auto", "Directory name with slideshow frames to render after installing update package",
) "update_default",
),
vars.Add( (
"UPDATE_SPLASH", "LOADER_AUTOSTART",
help="Directory name with slideshow frames to render after installing update package", "Application name to automatically run on Flipper boot",
default="update_default", "",
) ),
(
vars.Add( "FIRMWARE_APPS",
"LOADER_AUTOSTART", "Map of (configuration_name->application_list)",
help="Application name to automatically run on Flipper boot", {
default="", "default": (
) # Svc
"basic_services",
# Apps
vars.Add( "main_apps",
"FIRMWARE_APPS", "system_apps",
help="Map of (configuration_name->application_list)", # Settings
default={ "settings_apps",
"default": ( # Plugins
# Svc # "basic_plugins",
"basic_services", # Debug
# Apps # "debug_apps",
"main_apps", )
"system_apps", },
# Settings ),
"settings_apps", (
# Plugins "FIRMWARE_APP_SET",
# "basic_plugins", "Application set to use from FIRMWARE_APPS",
# Debug "default",
# "debug_apps", ),
) (
}, "APPSRC",
) "Application source directory for app to build & upload",
"",
vars.Add( ),
"FIRMWARE_APP_SET", # List of tuples (directory, add_to_global_include_path)
help="Application set to use from FIRMWARE_APPS", (
default="default", "APPDIRS",
) "Directories to search for firmware components & external apps",
[
vars.Add( ("applications", False),
"APPSRC", ("applications/services", True),
help="Application source directory for app to build & upload", ("applications/main", True),
default="", ("applications/settings", False),
) ("applications/system", False),
("applications/debug", False),
# List of tuples (directory, add_to_global_include_path) ("applications/plugins", False),
vars.Add( ("applications_user", False),
"APPDIRS", ],
help="Directories to search for firmware components & external apps", ),
default=[
("applications", False),
("applications/services", True),
("applications/main", True),
("applications/settings", False),
("applications/system", False),
("applications/debug", False),
("applications/plugins", False),
("applications/examples", False),
("applications_user", False),
],
) )
Return("vars") Return("vars")

View File

@ -1,6 +1,5 @@
import SCons
from SCons.Platform import TempFileMunge from SCons.Platform import TempFileMunge
from fbt import util from fbt.util import tempfile_arg_esc_func, single_quote, wrap_tempfile
import os import os
import multiprocessing import multiprocessing
@ -13,14 +12,18 @@ forward_os_env = {
} }
# Proxying CI environment to child processes & scripts # Proxying CI environment to child processes & scripts
variables_to_forward = [ variables_to_forward = [
# CI/CD variables
"WORKFLOW_BRANCH_OR_TAG", "WORKFLOW_BRANCH_OR_TAG",
"DIST_SUFFIX", "DIST_SUFFIX",
# Python & other tools
"HOME", "HOME",
"APPDATA", "APPDATA",
"PYTHONHOME", "PYTHONHOME",
"PYTHONNOUSERSITE", "PYTHONNOUSERSITE",
"TMP", "TMP",
"TEMP", "TEMP",
# Colors for tools
"TERM",
] ]
if proxy_env := GetOption("proxy_env"): if proxy_env := GetOption("proxy_env"):
variables_to_forward.extend(proxy_env.split(",")) variables_to_forward.extend(proxy_env.split(","))
@ -79,7 +82,7 @@ if not coreenv["VERBOSE"]:
SetOption("num_jobs", multiprocessing.cpu_count()) SetOption("num_jobs", multiprocessing.cpu_count())
# Avoiding re-scan of all sources on every startup # Avoiding re-scan of all sources on every startup
SetOption("implicit_cache", True) SetOption("implicit_cache", True)
SetOption("implicit_deps_unchanged", True) # SetOption("implicit_deps_unchanged", True)
# More aggressive caching # More aggressive caching
SetOption("max_drift", 1) SetOption("max_drift", 1)
# Random task queue - to discover isses with build logic faster # Random task queue - to discover isses with build logic faster
@ -87,10 +90,10 @@ SetOption("max_drift", 1)
# Setting up temp file parameters - to overcome command line length limits # Setting up temp file parameters - to overcome command line length limits
coreenv["TEMPFILEARGESCFUNC"] = util.tempfile_arg_esc_func coreenv["TEMPFILEARGESCFUNC"] = tempfile_arg_esc_func
util.wrap_tempfile(coreenv, "LINKCOM") wrap_tempfile(coreenv, "LINKCOM")
util.wrap_tempfile(coreenv, "ARCOM") wrap_tempfile(coreenv, "ARCOM")
coreenv["SINGLEQUOTEFUNC"] = util.single_quote coreenv["SINGLEQUOTEFUNC"] = single_quote
Return("coreenv") Return("coreenv")

View File

@ -21,7 +21,7 @@ appenv = ENV.Clone(
) )
appenv.Replace( appenv.Replace(
LINKER_SCRIPT="application-ext", LINKER_SCRIPT="application_ext",
) )
appenv.AppendUnique( appenv.AppendUnique(
@ -106,6 +106,7 @@ appenv.PhonyTarget("firmware_extapps", appenv.Action(legacy_app_build_stub, None
Alias("faps", extapps["compact"].values()) Alias("faps", extapps["compact"].values())
Alias("faps", extapps["validators"].values())
if appsrc := appenv.subst("$APPSRC"): if appsrc := appenv.subst("$APPSRC"):
app_manifest, fap_file, app_validator = appenv.GetExtAppFromPath(appsrc) app_manifest, fap_file, app_validator = appenv.GetExtAppFromPath(appsrc)

View File

@ -0,0 +1,23 @@
from fbt.util import link_dir
def link_elf_dir_as_latest(env, elf_node):
elf_dir = elf_node.Dir(".")
latest_dir = env.Dir("#build/latest")
print(f"Setting {elf_dir} as latest built dir (./build/latest/)")
return link_dir(latest_dir.abspath, elf_dir.abspath, env["PLATFORM"] == "win32")
def should_gen_cdb_and_link_dir(env, requested_targets):
explicitly_building_updater = False
# Hacky way to check if updater-related targets were requested
for build_target in requested_targets:
if "updater" in str(build_target):
explicitly_building_updater = True
is_updater = not env["IS_BASE_FIRMWARE"]
# If updater is explicitly requested, link to the latest updater
# Otherwise, link to firmware
return (is_updater and explicitly_building_updater) or (
not is_updater and not explicitly_building_updater
)