[FL-3162] Moved ufbt to fbt codebase (#2520)
* scripts: moved ufbt code * ufbt: fixed tool path * ufbt: fixed linter/formatter target descriptions * scripts: ufbt: cleanup * fbt: moved fap launch target to tools; ufbt fixes * fbt: fixed missing headers from SDK * ufbt: removed debug output * ufbt: moved project template to main codebase * ufbt: fixed vscode_dist * ufbt: path naming changes * fbt: error message for older ufbt versions * ufbt: docs fixes * ufbt: fixed build dir location * fbt: fixes for extapps objcopy * fbt: extapps: removed extra debug output; fixed formatting * ufbt: handle launch target for multiple known apps * ufbt: dropping wrapper; linter fixes * ufbt: fixed boostrap path * ufbt: renamed entrypoint * ufbt: updated vscode config * ufbt: moved sconsign db location * ufbt: fixed sconsign path * fbt: SDK builders rework * fbt: reworked sdk packaging * ufbt: additional checks and state processing * ufbt: fixed sdk state file location * dist: not packaging pycache * dump commit json content * Github: more workflow debug prints * Github: fix incorrect commit meta extraction in get_env.py * ufbt, fbt: changed SConsEnvironmentError->StopError * fbtenv: no longer needs SCRIPT_PATH pre-set * ufbt: fixed sdk state check * scripts: exception fixes for storage.py * scripts: fbtenv: added FBT_TOOLCHAIN_PATH for on Windows for compat * ufbt: app template: creating .gitkeep for images folder * ufbt: app template: fixed .gitkeep creation * docs: formatting fixes for AppManifests; added link to ufbt * fbt: added link to PyPI for old ufbt versions * sdk: fixed dir component paths Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									8a021ae48c
								
							
						
					
					
						commit
						a91d319839
					
				| @ -4,7 +4,7 @@ | |||||||
| #include <flipper_application/api_hashtable/compilesort.hpp> | #include <flipper_application/api_hashtable/compilesort.hpp> | ||||||
| 
 | 
 | ||||||
| /* Generated table */ | /* Generated table */ | ||||||
| #include <symbols.h> | #include <firmware_api_table.h> | ||||||
| 
 | 
 | ||||||
| static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); | static_assert(!has_hash_collisions(elf_api_table), "Detected API method hash collision!"); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3,6 +3,8 @@ | |||||||
| FBT is the entry point for firmware-related commands and utilities. | FBT is the entry point for firmware-related commands and utilities. | ||||||
| It is invoked by `./fbt` in the firmware project root directory. Internally, it is a wrapper around [scons](https://scons.org/) build system. | It is invoked by `./fbt` in the firmware project root directory. Internally, it is a wrapper around [scons](https://scons.org/) build system. | ||||||
| 
 | 
 | ||||||
|  | If you don't need all features of `fbt` - like building the whole firmware - and only want to build and debug a single application, you can use [ufbt](https://pypi.org/project/ufbt/). | ||||||
|  | 
 | ||||||
| ## Environment | ## Environment | ||||||
| 
 | 
 | ||||||
| To use `fbt`, you only need `git` installed in your system. | To use `fbt`, you only need `git` installed in your system. | ||||||
|  | |||||||
| @ -68,7 +68,7 @@ env = ENV.Clone( | |||||||
|             ], |             ], | ||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
|     SDK_APISYMS=None, |     FW_API_TABLE=None, | ||||||
|     _APP_ICONS=None, |     _APP_ICONS=None, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @ -241,7 +241,7 @@ Depends( | |||||||
|     [ |     [ | ||||||
|         fwenv["FW_VERSION_JSON"], |         fwenv["FW_VERSION_JSON"], | ||||||
|         fwenv["FW_ASSETS_HEADERS"], |         fwenv["FW_ASSETS_HEADERS"], | ||||||
|         fwenv["SDK_APISYMS"], |         fwenv["FW_API_TABLE"], | ||||||
|         fwenv["_APP_ICONS"], |         fwenv["_APP_ICONS"], | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| from SCons.Builder import Builder | from SCons.Builder import Builder | ||||||
| from SCons.Action import Action | from SCons.Action import Action | ||||||
| from SCons.Errors import SConsEnvironmentError | from SCons.Errors import StopError | ||||||
| 
 | 
 | ||||||
| import os | import os | ||||||
| import subprocess | import subprocess | ||||||
| @ -90,7 +90,7 @@ def proto_ver_generator(target, source, env): | |||||||
|             source_dir=src_dir, |             source_dir=src_dir, | ||||||
|         ) |         ) | ||||||
|     except (subprocess.CalledProcessError, EnvironmentError) as e: |     except (subprocess.CalledProcessError, EnvironmentError) as e: | ||||||
|         raise SConsEnvironmentError("Git: describe failed") |         raise StopError("Git: describe failed") | ||||||
| 
 | 
 | ||||||
|     git_major, git_minor = git_describe.split(".") |     git_major, git_minor = git_describe.split(".") | ||||||
|     version_file_data = ( |     version_file_data = ( | ||||||
|  | |||||||
| @ -21,6 +21,10 @@ from fbt.sdk.cache import SdkCache | |||||||
| from fbt.util import extract_abs_dir_path | from fbt.util import extract_abs_dir_path | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | _FAP_META_SECTION = ".fapmeta" | ||||||
|  | _FAP_FILEASSETS_SECTION = ".fapassets" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @dataclass | @dataclass | ||||||
| class FlipperExternalAppInfo: | class FlipperExternalAppInfo: | ||||||
|     app: FlipperApplication |     app: FlipperApplication | ||||||
| @ -234,6 +238,8 @@ def BuildAppElf(env, app): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def prepare_app_metadata(target, source, env): | def prepare_app_metadata(target, source, env): | ||||||
|  |     metadata_node = next(filter(lambda t: t.name.endswith(_FAP_META_SECTION), target)) | ||||||
|  | 
 | ||||||
|     sdk_cache = SdkCache(env["SDK_DEFINITION"].path, load_version_only=True) |     sdk_cache = SdkCache(env["SDK_DEFINITION"].path, load_version_only=True) | ||||||
| 
 | 
 | ||||||
|     if not sdk_cache.is_buildable(): |     if not sdk_cache.is_buildable(): | ||||||
| @ -242,8 +248,7 @@ def prepare_app_metadata(target, source, env): | |||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     app = env["APP"] |     app = env["APP"] | ||||||
|     meta_file_name = source[0].path + ".meta" |     with open(metadata_node.abspath, "wb") as f: | ||||||
|     with open(meta_file_name, "wb") as f: |  | ||||||
|         f.write( |         f.write( | ||||||
|             assemble_manifest_data( |             assemble_manifest_data( | ||||||
|                 app_manifest=app, |                 app_manifest=app, | ||||||
| @ -337,24 +342,26 @@ def embed_app_metadata_emitter(target, source, env): | |||||||
|     if app.apptype == FlipperAppType.PLUGIN: |     if app.apptype == FlipperAppType.PLUGIN: | ||||||
|         target[0].name = target[0].name.replace(".fap", ".fal") |         target[0].name = target[0].name.replace(".fap", ".fal") | ||||||
| 
 | 
 | ||||||
|     meta_file_name = source[0].path + ".meta" |     target.append(env.File(source[0].abspath + _FAP_META_SECTION)) | ||||||
|     target.append("#" + meta_file_name) |  | ||||||
| 
 | 
 | ||||||
|     if app.fap_file_assets: |     if app.fap_file_assets: | ||||||
|         files_section = source[0].path + ".files.section" |         target.append(env.File(source[0].abspath + _FAP_FILEASSETS_SECTION)) | ||||||
|         target.append("#" + files_section) |  | ||||||
| 
 | 
 | ||||||
|     return (target, source) |     return (target, source) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def prepare_app_files(target, source, env): | def prepare_app_files(target, source, env): | ||||||
|  |     files_section_node = next( | ||||||
|  |         filter(lambda t: t.name.endswith(_FAP_FILEASSETS_SECTION), target) | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|     app = env["APP"] |     app = env["APP"] | ||||||
|     directory = app._appdir.Dir(app.fap_file_assets) |     directory = env.Dir(app._apppath).Dir(app.fap_file_assets) | ||||||
|     if not directory.exists(): |     if not directory.exists(): | ||||||
|         raise UserError(f"File asset directory {directory} does not exist") |         raise UserError(f"File asset directory {directory} does not exist") | ||||||
| 
 | 
 | ||||||
|     bundler = FileBundler(directory.abspath) |     bundler = FileBundler(directory.abspath) | ||||||
|     bundler.export(source[0].path + ".files.section") |     bundler.export(files_section_node.abspath) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def generate_embed_app_metadata_actions(source, target, env, for_signature): | def generate_embed_app_metadata_actions(source, target, env, for_signature): | ||||||
| @ -367,15 +374,15 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): | |||||||
|     objcopy_str = ( |     objcopy_str = ( | ||||||
|         "${OBJCOPY} " |         "${OBJCOPY} " | ||||||
|         "--remove-section .ARM.attributes " |         "--remove-section .ARM.attributes " | ||||||
|         "--add-section .fapmeta=${SOURCE}.meta " |         "--add-section ${_FAP_META_SECTION}=${SOURCE}${_FAP_META_SECTION} " | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     if app.fap_file_assets: |     if app.fap_file_assets: | ||||||
|         actions.append(Action(prepare_app_files, "$APPFILE_COMSTR")) |         actions.append(Action(prepare_app_files, "$APPFILE_COMSTR")) | ||||||
|         objcopy_str += "--add-section .fapassets=${SOURCE}.files.section " |         objcopy_str += "--add-section ${_FAP_FILEASSETS_SECTION}=${SOURCE}${_FAP_FILEASSETS_SECTION} " | ||||||
| 
 | 
 | ||||||
|     objcopy_str += ( |     objcopy_str += ( | ||||||
|         "--set-section-flags .fapmeta=contents,noload,readonly,data " |         "--set-section-flags ${_FAP_META_SECTION}=contents,noload,readonly,data " | ||||||
|         "--strip-debug --strip-unneeded " |         "--strip-debug --strip-unneeded " | ||||||
|         "--add-gnu-debuglink=${SOURCE} " |         "--add-gnu-debuglink=${SOURCE} " | ||||||
|         "${SOURCES} ${TARGET}" |         "${SOURCES} ${TARGET}" | ||||||
| @ -391,6 +398,51 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): | |||||||
|     return Action(actions) |     return Action(actions) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def AddAppLaunchTarget(env, appname, launch_target_name): | ||||||
|  |     deploy_sources, flipp_dist_paths, validators = [], [], [] | ||||||
|  |     run_script_extra_ars = "" | ||||||
|  | 
 | ||||||
|  |     def _add_dist_targets(app_artifacts): | ||||||
|  |         validators.append(app_artifacts.validator) | ||||||
|  |         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 | ||||||
|  | 
 | ||||||
|  |     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) | ||||||
|  |         for plugin in host_app._plugins: | ||||||
|  |             _add_dist_targets(env["EXT_APPS"].get(plugin.appid, None)) | ||||||
|  | 
 | ||||||
|  |     artifacts_app_to_run = env.GetExtAppByIdOrPath(appname) | ||||||
|  |     if artifacts_app_to_run.app.apptype == FlipperAppType.PLUGIN: | ||||||
|  |         # We deploy host app instead | ||||||
|  |         host_app = env["APPMGR"].get(artifacts_app_to_run.app.requires[0]) | ||||||
|  | 
 | ||||||
|  |         if host_app: | ||||||
|  |             if host_app.apptype == FlipperAppType.EXTERNAL: | ||||||
|  |                 _add_host_app_to_targets(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) | ||||||
|  |         else: | ||||||
|  |             raise UserError("Host app is unknown") | ||||||
|  |     else: | ||||||
|  |         _add_host_app_to_targets(artifacts_app_to_run.app) | ||||||
|  | 
 | ||||||
|  |     # print(deploy_sources, flipp_dist_paths) | ||||||
|  |     env.PhonyTarget( | ||||||
|  |         launch_target_name, | ||||||
|  |         '${PYTHON3} "${APP_RUN_SCRIPT}" ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', | ||||||
|  |         source=deploy_sources, | ||||||
|  |         FLIPPER_FILE_TARGETS=flipp_dist_paths, | ||||||
|  |         EXTRA_ARGS=run_script_extra_ars, | ||||||
|  |     ) | ||||||
|  |     env.Alias(launch_target_name, validators) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def generate(env, **kw): | def generate(env, **kw): | ||||||
|     env.SetDefault( |     env.SetDefault( | ||||||
|         EXT_APPS_WORK_DIR="${FBT_FAP_DEBUG_ELF_ROOT}", |         EXT_APPS_WORK_DIR="${FBT_FAP_DEBUG_ELF_ROOT}", | ||||||
| @ -410,10 +462,14 @@ def generate(env, **kw): | |||||||
|         EXT_APPS={},  # appid -> FlipperExternalAppInfo |         EXT_APPS={},  # appid -> FlipperExternalAppInfo | ||||||
|         EXT_LIBS={}, |         EXT_LIBS={}, | ||||||
|         _APP_ICONS=[], |         _APP_ICONS=[], | ||||||
|  |         _FAP_META_SECTION=_FAP_META_SECTION, | ||||||
|  |         _FAP_FILEASSETS_SECTION=_FAP_FILEASSETS_SECTION, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     env.AddMethod(BuildAppElf) |     env.AddMethod(BuildAppElf) | ||||||
|     env.AddMethod(GetExtAppByIdOrPath) |     env.AddMethod(GetExtAppByIdOrPath) | ||||||
|  |     env.AddMethod(AddAppLaunchTarget) | ||||||
|  | 
 | ||||||
|     env.Append( |     env.Append( | ||||||
|         BUILDERS={ |         BUILDERS={ | ||||||
|             "FapDist": Builder( |             "FapDist": Builder( | ||||||
|  | |||||||
| @ -38,13 +38,13 @@ def ProcessSdkDepends(env, filename): | |||||||
|     return depends |     return depends | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def prebuild_sdk_emitter(target, source, env): | def api_amalgam_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")) | ||||||
|     return target, source |     return target, source | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def prebuild_sdk_create_origin_file(target, source, env): | def api_amalgam_gen_origin_header(target, source, env): | ||||||
|     mega_file = env.subst("${TARGET}.c", target=target[0]) |     mega_file = env.subst("${TARGET}.c", target=target[0]) | ||||||
|     with open(mega_file, "wt") as sdk_c: |     with open(mega_file, "wt") as sdk_c: | ||||||
|         sdk_c.write( |         sdk_c.write( | ||||||
| @ -87,6 +87,7 @@ class SdkMeta: | |||||||
| class SdkTreeBuilder: | class SdkTreeBuilder: | ||||||
|     SDK_DIR_SUBST = "SDK_ROOT_DIR" |     SDK_DIR_SUBST = "SDK_ROOT_DIR" | ||||||
|     SDK_APP_EP_SUBST = "SDK_APP_EP_SUBST" |     SDK_APP_EP_SUBST = "SDK_APP_EP_SUBST" | ||||||
|  |     HEADER_EXTENSIONS = [".h", ".hpp"] | ||||||
| 
 | 
 | ||||||
|     def __init__(self, env, target, source) -> None: |     def __init__(self, env, target, source) -> None: | ||||||
|         self.env = env |         self.env = env | ||||||
| @ -111,7 +112,10 @@ class SdkTreeBuilder: | |||||||
|             lines = LogicalLines(deps_f).readlines() |             lines = LogicalLines(deps_f).readlines() | ||||||
|             _, depends = lines[0].split(":", 1) |             _, depends = lines[0].split(":", 1) | ||||||
|             self.header_depends = list( |             self.header_depends = list( | ||||||
|                 filter(lambda fname: fname.endswith(".h"), depends.split()), |                 filter( | ||||||
|  |                     lambda fname: any(map(fname.endswith, self.HEADER_EXTENSIONS)), | ||||||
|  |                     depends.split(), | ||||||
|  |                 ), | ||||||
|             ) |             ) | ||||||
|             self.header_depends.append(self.sdk_env.subst("${LINKER_SCRIPT_PATH}")) |             self.header_depends.append(self.sdk_env.subst("${LINKER_SCRIPT_PATH}")) | ||||||
|             self.header_depends.append(self.sdk_env.subst("${SDK_DEFINITION}")) |             self.header_depends.append(self.sdk_env.subst("${SDK_DEFINITION}")) | ||||||
| @ -180,12 +184,12 @@ class SdkTreeBuilder: | |||||||
|         self._generate_sdk_meta() |         self._generate_sdk_meta() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def deploy_sdk_tree_action(target, source, env): | def deploy_sdk_header_tree_action(target, source, env): | ||||||
|     sdk_tree = SdkTreeBuilder(env, target, source) |     sdk_tree = SdkTreeBuilder(env, target, source) | ||||||
|     return sdk_tree.deploy_action() |     return sdk_tree.deploy_action() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def deploy_sdk_tree_emitter(target, source, env): | def deploy_sdk_header_tree_emitter(target, source, env): | ||||||
|     sdk_tree = SdkTreeBuilder(env, target, source) |     sdk_tree = SdkTreeBuilder(env, target, source) | ||||||
|     return sdk_tree.emitter(target, source, env) |     return sdk_tree.emitter(target, source, env) | ||||||
| 
 | 
 | ||||||
| @ -224,7 +228,7 @@ def _check_sdk_is_up2date(sdk_cache: SdkCache): | |||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def validate_sdk_cache(source, target, env): | def validate_api_cache(source, target, env): | ||||||
|     # print(f"Generating SDK for {source[0]} to {target[0]}") |     # print(f"Generating SDK for {source[0]} to {target[0]}") | ||||||
|     current_sdk = SdkCollector() |     current_sdk = SdkCollector() | ||||||
|     current_sdk.process_source_file_for_sdk(source[0].path) |     current_sdk.process_source_file_for_sdk(source[0].path) | ||||||
| @ -237,7 +241,7 @@ def validate_sdk_cache(source, target, env): | |||||||
|     _check_sdk_is_up2date(sdk_cache) |     _check_sdk_is_up2date(sdk_cache) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def generate_sdk_symbols(source, target, env): | def generate_api_table(source, target, env): | ||||||
|     sdk_cache = SdkCache(source[0].path) |     sdk_cache = SdkCache(source[0].path) | ||||||
|     _check_sdk_is_up2date(sdk_cache) |     _check_sdk_is_up2date(sdk_cache) | ||||||
| 
 | 
 | ||||||
| @ -249,11 +253,11 @@ def generate_sdk_symbols(source, target, env): | |||||||
| def generate(env, **kw): | def generate(env, **kw): | ||||||
|     if not env["VERBOSE"]: |     if not env["VERBOSE"]: | ||||||
|         env.SetDefault( |         env.SetDefault( | ||||||
|             SDK_PREGEN_COMSTR="\tPREGEN\t${TARGET}", |             SDK_AMALGAMATE_HEADER_COMSTR="\tAPIPREP\t${TARGET}", | ||||||
|             SDK_COMSTR="\tSDKSRC\t${TARGET}", |             SDK_AMALGAMATE_PP_COMSTR="\tAPIPP\t${TARGET}", | ||||||
|             SDKSYM_UPDATER_COMSTR="\tSDKCHK\t${TARGET}", |             SDKSYM_UPDATER_COMSTR="\tSDKCHK\t${TARGET}", | ||||||
|             SDKSYM_GENERATOR_COMSTR="\tSDKSYM\t${TARGET}", |             APITABLE_GENERATOR_COMSTR="\tAPITBL\t${TARGET}", | ||||||
|             SDKDEPLOY_COMSTR="\tSDKTREE\t${TARGET}", |             SDKTREE_COMSTR="\tSDKTREE\t${TARGET}", | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     # Filtering out things cxxheaderparser cannot handle |     # Filtering out things cxxheaderparser cannot handle | ||||||
| @ -274,40 +278,40 @@ def generate(env, **kw): | |||||||
|     env.AddMethod(ProcessSdkDepends) |     env.AddMethod(ProcessSdkDepends) | ||||||
|     env.Append( |     env.Append( | ||||||
|         BUILDERS={ |         BUILDERS={ | ||||||
|             "SDKPrebuilder": Builder( |             "ApiAmalgamator": Builder( | ||||||
|                 emitter=prebuild_sdk_emitter, |                 emitter=api_amalgam_emitter, | ||||||
|                 action=[ |                 action=[ | ||||||
|                     Action( |                     Action( | ||||||
|                         prebuild_sdk_create_origin_file, |                         api_amalgam_gen_origin_header, | ||||||
|                         "$SDK_PREGEN_COMSTR", |                         "$SDK_AMALGAMATE_HEADER_COMSTR", | ||||||
|                     ), |                     ), | ||||||
|                     Action( |                     Action( | ||||||
|                         "$CC -o $TARGET -E -P $CCFLAGS $_CCCOMCOM $SDK_PP_FLAGS -MMD ${TARGET}.c", |                         "$CC -o $TARGET -E -P $CCFLAGS $_CCCOMCOM $SDK_PP_FLAGS -MMD ${TARGET}.c", | ||||||
|                         "$SDK_COMSTR", |                         "$SDK_AMALGAMATE_PP_COMSTR", | ||||||
|                     ), |                     ), | ||||||
|                 ], |                 ], | ||||||
|                 suffix=".i", |                 suffix=".i", | ||||||
|             ), |             ), | ||||||
|             "SDKTree": Builder( |             "SDKHeaderTreeExtractor": Builder( | ||||||
|                 action=Action( |                 action=Action( | ||||||
|                     deploy_sdk_tree_action, |                     deploy_sdk_header_tree_action, | ||||||
|                     "$SDKDEPLOY_COMSTR", |                     "$SDKTREE_COMSTR", | ||||||
|                 ), |                 ), | ||||||
|                 emitter=deploy_sdk_tree_emitter, |                 emitter=deploy_sdk_header_tree_emitter, | ||||||
|                 src_suffix=".d", |                 src_suffix=".d", | ||||||
|             ), |             ), | ||||||
|             "SDKSymUpdater": Builder( |             "ApiTableValidator": Builder( | ||||||
|                 action=Action( |                 action=Action( | ||||||
|                     validate_sdk_cache, |                     validate_api_cache, | ||||||
|                     "$SDKSYM_UPDATER_COMSTR", |                     "$SDKSYM_UPDATER_COMSTR", | ||||||
|                 ), |                 ), | ||||||
|                 suffix=".csv", |                 suffix=".csv", | ||||||
|                 src_suffix=".i", |                 src_suffix=".i", | ||||||
|             ), |             ), | ||||||
|             "SDKSymGenerator": Builder( |             "ApiSymbolTable": Builder( | ||||||
|                 action=Action( |                 action=Action( | ||||||
|                     generate_sdk_symbols, |                     generate_api_table, | ||||||
|                     "$SDKSYM_GENERATOR_COMSTR", |                     "$APITABLE_GENERATOR_COMSTR", | ||||||
|                 ), |                 ), | ||||||
|                 suffix=".h", |                 suffix=".h", | ||||||
|                 src_suffix=".csv", |                 src_suffix=".csv", | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| import SCons.Warnings as Warnings | import SCons.Warnings as Warnings | ||||||
|  | from SCons.Errors import UserError | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| # from SCons.Script.Main import find_deepest_user_frame | # from SCons.Script.Main import find_deepest_user_frame | ||||||
| 
 | 
 | ||||||
| @ -36,6 +38,11 @@ def fbt_warning(e): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def generate(env): | def generate(env): | ||||||
|  |     if env.get("UFBT_WORK_DIR"): | ||||||
|  |         raise UserError( | ||||||
|  |             "You're trying to use a new format SDK on a legacy ufbt version. " | ||||||
|  |             "Please update ufbt to a version from PyPI: https://pypi.org/project/ufbt/" | ||||||
|  |         ) | ||||||
|     Warnings._warningOut = fbt_warning |     Warnings._warningOut = fbt_warning | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -56,11 +56,11 @@ class StorageErrorCode(enum.Enum): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class FlipperStorageException(Exception): | class FlipperStorageException(Exception): | ||||||
|     def __init__(self, message): |     @staticmethod | ||||||
|         super().__init__(f"Storage error: {message}") |     def from_error_code(path: str, error_code: StorageErrorCode): | ||||||
| 
 |         return FlipperStorageException( | ||||||
|     def __init__(self, path: str, error_code: StorageErrorCode): |             f"Storage error: path '{path}': {error_code.value}" | ||||||
|         super().__init__(f"Storage error: path '{path}': {error_code.value}") |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class BufferedRead: | class BufferedRead: | ||||||
| @ -247,7 +247,9 @@ class FlipperStorage: | |||||||
|                 if self.has_error(answer): |                 if self.has_error(answer): | ||||||
|                     last_error = self.get_error(answer) |                     last_error = self.get_error(answer) | ||||||
|                     self.read.until(self.CLI_PROMPT) |                     self.read.until(self.CLI_PROMPT) | ||||||
|                     raise FlipperStorageException(filename_to, last_error) |                     raise FlipperStorageException.from_error_code( | ||||||
|  |                         filename_to, last_error | ||||||
|  |                     ) | ||||||
| 
 | 
 | ||||||
|                 self.port.write(filedata) |                 self.port.write(filedata) | ||||||
|                 self.read.until(self.CLI_PROMPT) |                 self.read.until(self.CLI_PROMPT) | ||||||
| @ -319,7 +321,7 @@ class FlipperStorage: | |||||||
|                 StorageErrorCode.INVALID_NAME, |                 StorageErrorCode.INVALID_NAME, | ||||||
|             ): |             ): | ||||||
|                 return False |                 return False | ||||||
|             raise FlipperStorageException(path, error_code) |             raise FlipperStorageException.from_error_code(path, error_code) | ||||||
| 
 | 
 | ||||||
|         return True |         return True | ||||||
| 
 | 
 | ||||||
| @ -333,7 +335,7 @@ class FlipperStorage: | |||||||
| 
 | 
 | ||||||
|     def _check_no_error(self, response, path=None): |     def _check_no_error(self, response, path=None): | ||||||
|         if self.has_error(response): |         if self.has_error(response): | ||||||
|             raise FlipperStorageException(self.get_error(response)) |             raise FlipperStorageException.from_error_code(self.get_error(response)) | ||||||
| 
 | 
 | ||||||
|     def size(self, path: str): |     def size(self, path: str): | ||||||
|         """file size on Flipper""" |         """file size on Flipper""" | ||||||
|  | |||||||
| @ -31,9 +31,10 @@ def parse_args(): | |||||||
| 
 | 
 | ||||||
| def get_commit_json(event): | def get_commit_json(event): | ||||||
|     context = ssl._create_unverified_context() |     context = ssl._create_unverified_context() | ||||||
|     with urllib.request.urlopen( |     commit_url = event["pull_request"]["base"]["repo"]["commits_url"].replace( | ||||||
|         event["pull_request"]["_links"]["commits"]["href"], context=context |         "{/sha}", f"/{event['after']}" | ||||||
|     ) as commit_file: |     ) | ||||||
|  |     with urllib.request.urlopen(commit_url, context=context) as commit_file: | ||||||
|         commit_json = json.loads(commit_file.read().decode("utf-8")) |         commit_json = json.loads(commit_file.read().decode("utf-8")) | ||||||
|     return commit_json |     return commit_json | ||||||
| 
 | 
 | ||||||
| @ -43,8 +44,8 @@ def get_details(event, args): | |||||||
|     current_time = datetime.datetime.utcnow().date() |     current_time = datetime.datetime.utcnow().date() | ||||||
|     if args.type == "pull": |     if args.type == "pull": | ||||||
|         commit_json = get_commit_json(event) |         commit_json = get_commit_json(event) | ||||||
|         data["commit_comment"] = shlex.quote(commit_json[-1]["commit"]["message"]) |         data["commit_comment"] = shlex.quote(commit_json["commit"]["message"]) | ||||||
|         data["commit_hash"] = commit_json[-1]["sha"] |         data["commit_hash"] = commit_json["sha"] | ||||||
|         ref = event["pull_request"]["head"]["ref"] |         ref = event["pull_request"]["head"]["ref"] | ||||||
|         data["pull_id"] = event["pull_request"]["number"] |         data["pull_id"] = event["pull_request"]["number"] | ||||||
|         data["pull_name"] = shlex.quote(event["pull_request"]["title"]) |         data["pull_name"] = shlex.quote(event["pull_request"]["title"]) | ||||||
|  | |||||||
| @ -1,13 +1,15 @@ | |||||||
| #!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||||
| 
 | 
 | ||||||
| from flipper.app import App | import json | ||||||
| from os.path import join, exists, relpath |  | ||||||
| from os import makedirs, walk |  | ||||||
| from update import Main as UpdateMain |  | ||||||
| import shutil | import shutil | ||||||
| import zipfile |  | ||||||
| import tarfile | import tarfile | ||||||
|  | import zipfile | ||||||
|  | from os import makedirs, walk | ||||||
|  | from os.path import exists, join, relpath, basename, split | ||||||
|  | 
 | ||||||
| from ansi.color import fg | from ansi.color import fg | ||||||
|  | from flipper.app import App | ||||||
|  | from update import Main as UpdateMain | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ProjectDir: | class ProjectDir: | ||||||
| @ -54,12 +56,19 @@ class Main(App): | |||||||
|         if project_name == "firmware" and filetype != "elf": |         if project_name == "firmware" and filetype != "elf": | ||||||
|             project_name = "full" |             project_name = "full" | ||||||
| 
 | 
 | ||||||
|         return self.get_dist_file_name(project_name, filetype) |         dist_target_path = self.get_dist_file_name(project_name, filetype) | ||||||
|  |         self.note_dist_component( | ||||||
|  |             project_name, filetype, self.get_dist_path(dist_target_path) | ||||||
|  |         ) | ||||||
|  |         return dist_target_path | ||||||
|  | 
 | ||||||
|  |     def note_dist_component(self, component: str, extension: str, srcpath: str) -> None: | ||||||
|  |         self._dist_components[f"{component}.{extension}"] = srcpath | ||||||
| 
 | 
 | ||||||
|     def get_dist_file_name(self, dist_artifact_type: str, filetype: str) -> str: |     def get_dist_file_name(self, dist_artifact_type: str, filetype: str) -> str: | ||||||
|         return f"{self.DIST_FILE_PREFIX}{self.target}-{dist_artifact_type}-{self.args.suffix}.{filetype}" |         return f"{self.DIST_FILE_PREFIX}{self.target}-{dist_artifact_type}-{self.args.suffix}.{filetype}" | ||||||
| 
 | 
 | ||||||
|     def get_dist_file_path(self, filename: str) -> str: |     def get_dist_path(self, filename: str) -> str: | ||||||
|         return join(self.output_dir_path, filename) |         return join(self.output_dir_path, filename) | ||||||
| 
 | 
 | ||||||
|     def copy_single_project(self, project: ProjectDir) -> None: |     def copy_single_project(self, project: ProjectDir) -> None: | ||||||
| @ -69,17 +78,15 @@ class Main(App): | |||||||
|             if exists(src_file := join(obj_directory, f"{project.project}.{filetype}")): |             if exists(src_file := join(obj_directory, f"{project.project}.{filetype}")): | ||||||
|                 shutil.copyfile( |                 shutil.copyfile( | ||||||
|                     src_file, |                     src_file, | ||||||
|                     self.get_dist_file_path( |                     self.get_dist_path(self.get_project_file_name(project, filetype)), | ||||||
|                         self.get_project_file_name(project, filetype) |  | ||||||
|                     ), |  | ||||||
|                 ) |                 ) | ||||||
|         for foldertype in ("sdk", "lib"): |         for foldertype in ("sdk_headers", "lib"): | ||||||
|             if exists(sdk_folder := join(obj_directory, foldertype)): |             if exists(sdk_folder := join(obj_directory, foldertype)): | ||||||
|                 self.package_zip(foldertype, sdk_folder) |                 self.note_dist_component(foldertype, "dir", sdk_folder) | ||||||
| 
 | 
 | ||||||
|     def package_zip(self, foldertype, sdk_folder): |     def package_zip(self, foldertype, sdk_folder): | ||||||
|         with zipfile.ZipFile( |         with zipfile.ZipFile( | ||||||
|             self.get_dist_file_path(self.get_dist_file_name(foldertype, "zip")), |             self.get_dist_path(self.get_dist_file_name(foldertype, "zip")), | ||||||
|             "w", |             "w", | ||||||
|             zipfile.ZIP_DEFLATED, |             zipfile.ZIP_DEFLATED, | ||||||
|         ) as zf: |         ) as zf: | ||||||
| @ -94,7 +101,8 @@ class Main(App): | |||||||
|                     ) |                     ) | ||||||
| 
 | 
 | ||||||
|     def copy(self) -> int: |     def copy(self) -> int: | ||||||
|         self.projects = dict( |         self._dist_components: dict[str, str] = dict() | ||||||
|  |         self.projects: dict[str, ProjectDir] = dict( | ||||||
|             map( |             map( | ||||||
|                 lambda pd: (pd.project, pd), |                 lambda pd: (pd.project, pd), | ||||||
|                 map(ProjectDir, self.args.project), |                 map(ProjectDir, self.args.project), | ||||||
| @ -122,12 +130,18 @@ class Main(App): | |||||||
|             try: |             try: | ||||||
|                 shutil.rmtree(self.output_dir_path) |                 shutil.rmtree(self.output_dir_path) | ||||||
|             except Exception as ex: |             except Exception as ex: | ||||||
|                 pass |                 self.logger.warn(f"Failed to clean output directory: {ex}") | ||||||
| 
 | 
 | ||||||
|         if not exists(self.output_dir_path): |         if not exists(self.output_dir_path): | ||||||
|  |             self.logger.debug(f"Creating output directory {self.output_dir_path}") | ||||||
|             makedirs(self.output_dir_path) |             makedirs(self.output_dir_path) | ||||||
| 
 | 
 | ||||||
|  |         for folder in ("debug", "scripts"): | ||||||
|  |             if exists(folder): | ||||||
|  |                 self.note_dist_component(folder, "dir", folder) | ||||||
|  | 
 | ||||||
|         for project in self.projects.values(): |         for project in self.projects.values(): | ||||||
|  |             self.logger.debug(f"Copying {project.project} for {project.target}") | ||||||
|             self.copy_single_project(project) |             self.copy_single_project(project) | ||||||
| 
 | 
 | ||||||
|         self.logger.info( |         self.logger.info( | ||||||
| @ -137,10 +151,83 @@ class Main(App): | |||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         if self.args.version: |         if self.args.version: | ||||||
|  |             if bundle_result := self.bundle_update_package(): | ||||||
|  |                 return bundle_result | ||||||
|  | 
 | ||||||
|  |         required_components = ("firmware.elf", "full.bin", "update.dir") | ||||||
|  |         if all( | ||||||
|  |             map( | ||||||
|  |                 lambda c: c in self._dist_components, | ||||||
|  |                 required_components, | ||||||
|  |             ) | ||||||
|  |         ): | ||||||
|  |             self.bundle_sdk() | ||||||
|  | 
 | ||||||
|  |         return 0 | ||||||
|  | 
 | ||||||
|  |     def bundle_sdk(self): | ||||||
|  |         self.logger.info("Bundling SDK") | ||||||
|  |         components_paths = dict() | ||||||
|  | 
 | ||||||
|  |         sdk_components_keys = ( | ||||||
|  |             "full.bin", | ||||||
|  |             "firmware.elf", | ||||||
|  |             "update.dir", | ||||||
|  |             "sdk_headers.dir", | ||||||
|  |             "lib.dir", | ||||||
|  |             "debug.dir", | ||||||
|  |             "scripts.dir", | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         with zipfile.ZipFile( | ||||||
|  |             self.get_dist_path(self.get_dist_file_name("sdk", "zip")), | ||||||
|  |             "w", | ||||||
|  |             zipfile.ZIP_DEFLATED, | ||||||
|  |         ) as zf: | ||||||
|  |             for component_key in sdk_components_keys: | ||||||
|  |                 component_path = self._dist_components.get(component_key) | ||||||
|  |                 components_paths[component_key] = basename(component_path) | ||||||
|  | 
 | ||||||
|  |                 if component_key.endswith(".dir"): | ||||||
|  |                     for root, dirnames, files in walk(component_path): | ||||||
|  |                         if "__pycache__" in dirnames: | ||||||
|  |                             dirnames.remove("__pycache__") | ||||||
|  |                         for file in files: | ||||||
|  |                             zf.write( | ||||||
|  |                                 join(root, file), | ||||||
|  |                                 join( | ||||||
|  |                                     components_paths[component_key], | ||||||
|  |                                     relpath( | ||||||
|  |                                         join(root, file), | ||||||
|  |                                         component_path, | ||||||
|  |                                     ), | ||||||
|  |                                 ), | ||||||
|  |                             ) | ||||||
|  |                 else: | ||||||
|  |                     zf.write(component_path, basename(component_path)) | ||||||
|  | 
 | ||||||
|  |             zf.writestr( | ||||||
|  |                 "components.json", | ||||||
|  |                 json.dumps( | ||||||
|  |                     { | ||||||
|  |                         "meta": { | ||||||
|  |                             "hw_target": self.target, | ||||||
|  |                             "flavor": self.flavor, | ||||||
|  |                             "version": self.args.version, | ||||||
|  |                         }, | ||||||
|  |                         "components": components_paths, | ||||||
|  |                     } | ||||||
|  |                 ), | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |     def bundle_update_package(self): | ||||||
|  |         self.logger.debug( | ||||||
|  |             f"Generating update bundle with version {self.args.version} for {self.target}" | ||||||
|  |         ) | ||||||
|         bundle_dir_name = f"{self.target}-update-{self.args.suffix}"[ |         bundle_dir_name = f"{self.target}-update-{self.args.suffix}"[ | ||||||
|             : self.DIST_FOLDER_MAX_NAME_LENGTH |             : self.DIST_FOLDER_MAX_NAME_LENGTH | ||||||
|         ] |         ] | ||||||
|             bundle_dir = join(self.output_dir_path, bundle_dir_name) |         bundle_dir = self.get_dist_path(bundle_dir_name) | ||||||
|         bundle_args = [ |         bundle_args = [ | ||||||
|             "generate", |             "generate", | ||||||
|             "-d", |             "-d", | ||||||
| @ -150,11 +237,11 @@ class Main(App): | |||||||
|             "-t", |             "-t", | ||||||
|             self.target, |             self.target, | ||||||
|             "--dfu", |             "--dfu", | ||||||
|                 self.get_dist_file_path( |             self.get_dist_path( | ||||||
|                 self.get_project_file_name(self.projects["firmware"], "dfu") |                 self.get_project_file_name(self.projects["firmware"], "dfu") | ||||||
|             ), |             ), | ||||||
|             "--stage", |             "--stage", | ||||||
|                 self.get_dist_file_path( |             self.get_dist_path( | ||||||
|                 self.get_project_file_name(self.projects["updater"], "bin") |                 self.get_project_file_name(self.projects["updater"], "bin") | ||||||
|             ), |             ), | ||||||
|         ] |         ] | ||||||
| @ -168,6 +255,7 @@ class Main(App): | |||||||
|         bundle_args.extend(self.other_args) |         bundle_args.extend(self.other_args) | ||||||
| 
 | 
 | ||||||
|         if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: |         if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: | ||||||
|  |             self.note_dist_component("update", "dir", bundle_dir) | ||||||
|             self.logger.info( |             self.logger.info( | ||||||
|                 fg.boldgreen( |                 fg.boldgreen( | ||||||
|                     f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" |                     f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" | ||||||
| @ -178,18 +266,18 @@ class Main(App): | |||||||
|             with tarfile.open( |             with tarfile.open( | ||||||
|                 join( |                 join( | ||||||
|                     self.output_dir_path, |                     self.output_dir_path, | ||||||
|                         f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz", |                     bundle_tgz := f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz", | ||||||
|                 ), |                 ), | ||||||
|                 "w:gz", |                 "w:gz", | ||||||
|                 compresslevel=9, |                 compresslevel=9, | ||||||
|                 format=tarfile.USTAR_FORMAT, |                 format=tarfile.USTAR_FORMAT, | ||||||
|             ) as tar: |             ) as tar: | ||||||
|  |                 self.note_dist_component( | ||||||
|  |                     "update", "tgz", self.get_dist_path(bundle_tgz) | ||||||
|  |                 ) | ||||||
|                 tar.add(bundle_dir, arcname=bundle_dir_name) |                 tar.add(bundle_dir, arcname=bundle_dir_name) | ||||||
| 
 |  | ||||||
|         return bundle_result |         return bundle_result | ||||||
| 
 | 
 | ||||||
|         return 0 |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     Main()() |     Main()() | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ import sys | |||||||
| def main(): | def main(): | ||||||
|     logger = logging.getLogger() |     logger = logging.getLogger() | ||||||
|     if not (port := resolve_port(logger, "auto")): |     if not (port := resolve_port(logger, "auto")): | ||||||
|         logger.error("Is Flipper connected over USB and isn't in DFU mode?") |         logger.error("Is Flipper connected over USB and is it not in DFU mode?") | ||||||
|         return 1 |         return 1 | ||||||
|     subprocess.call( |     subprocess.call( | ||||||
|         [ |         [ | ||||||
|  | |||||||
| @ -15,10 +15,12 @@ if not ["%FBT_NOENV%"] == [""] ( | |||||||
| 
 | 
 | ||||||
| set "FLIPPER_TOOLCHAIN_VERSION=21" | set "FLIPPER_TOOLCHAIN_VERSION=21" | ||||||
| 
 | 
 | ||||||
| if ["%FBT_TOOLCHAIN_ROOT%"] == [""] ( | if ["%FBT_TOOLCHAIN_PATH%"] == [""] ( | ||||||
|     set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" |     set "FBT_TOOLCHAIN_PATH=%FBT_ROOT%" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | set "FBT_TOOLCHAIN_ROOT=%FBT_TOOLCHAIN_PATH%\toolchain\x86_64-windows" | ||||||
|  | 
 | ||||||
| set "FBT_TOOLCHAIN_VERSION_FILE=%FBT_TOOLCHAIN_ROOT%\VERSION" | set "FBT_TOOLCHAIN_VERSION_FILE=%FBT_TOOLCHAIN_ROOT%\VERSION" | ||||||
| 
 | 
 | ||||||
| if not exist "%FBT_TOOLCHAIN_ROOT%" ( | if not exist "%FBT_TOOLCHAIN_ROOT%" ( | ||||||
|  | |||||||
| @ -4,9 +4,15 @@ | |||||||
| 
 | 
 | ||||||
| # public variables | # public variables | ||||||
| DEFAULT_SCRIPT_PATH="$(pwd -P)"; | DEFAULT_SCRIPT_PATH="$(pwd -P)"; | ||||||
| SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; |  | ||||||
| FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"21"}"; | FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"21"}"; | ||||||
| FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; | 
 | ||||||
|  | if [ -z ${FBT_TOOLCHAIN_PATH+x} ] ; then | ||||||
|  |     FBT_TOOLCHAIN_PATH_WAS_SET=0; | ||||||
|  | else | ||||||
|  |     FBT_TOOLCHAIN_PATH_WAS_SET=1; | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$DEFAULT_SCRIPT_PATH}"; | ||||||
| FBT_VERBOSE="${FBT_VERBOSE:-""}"; | FBT_VERBOSE="${FBT_VERBOSE:-""}"; | ||||||
| 
 | 
 | ||||||
| fbtenv_show_usage() | fbtenv_show_usage() | ||||||
| @ -60,7 +66,6 @@ fbtenv_restore_env() | |||||||
|     unset SAVED_PYTHONPATH; |     unset SAVED_PYTHONPATH; | ||||||
|     unset SAVED_PYTHONHOME; |     unset SAVED_PYTHONHOME; | ||||||
| 
 | 
 | ||||||
|     unset SCRIPT_PATH; |  | ||||||
|     unset FBT_TOOLCHAIN_VERSION; |     unset FBT_TOOLCHAIN_VERSION; | ||||||
|     unset FBT_TOOLCHAIN_PATH; |     unset FBT_TOOLCHAIN_PATH; | ||||||
| } | } | ||||||
| @ -104,13 +109,14 @@ fbtenv_set_shell_prompt() | |||||||
|     return 0;  # all other shells |     return 0;  # all other shells | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fbtenv_check_script_path() | fbtenv_check_env_vars() | ||||||
| { | { | ||||||
|     if [ ! -x "$SCRIPT_PATH/fbt" ] && [ ! -x "$SCRIPT_PATH/ufbt" ] ; then |     # Return error if FBT_TOOLCHAIN_PATH is not set before script is sourced or if fbt executable is not in DEFAULT_SCRIPT_PATH | ||||||
|         echo "Please source this script from [u]fbt root directory, or specify 'SCRIPT_PATH' variable manually"; |     if [ "$FBT_TOOLCHAIN_PATH_WAS_SET" -eq 0 ] && [ ! -x "$DEFAULT_SCRIPT_PATH/fbt" ] && [ ! -x "$DEFAULT_SCRIPT_PATH/ufbt" ] ; then | ||||||
|  |         echo "Please source this script from [u]fbt root directory, or specify 'FBT_TOOLCHAIN_PATH' variable manually"; | ||||||
|         echo "Example:"; |         echo "Example:"; | ||||||
|         printf "\tSCRIPT_PATH=lang/c/flipperzero-firmware source lang/c/flipperzero-firmware/scripts/fbtenv.sh\n"; |         printf "\tFBT_TOOLCHAIN_PATH=lang/c/flipperzero-firmware source lang/c/flipperzero-firmware/scripts/fbtenv.sh\n"; | ||||||
|         echo "If current directory is right, type 'unset SCRIPT_PATH' and try again" |         echo "If current directory is right, type 'unset FBT_TOOLCHAIN_PATH' and try again" | ||||||
|         return 1; |         return 1; | ||||||
|     fi |     fi | ||||||
|     return 0; |     return 0; | ||||||
| @ -207,7 +213,7 @@ fbtenv_show_unpack_percentage() | |||||||
| 
 | 
 | ||||||
| fbtenv_unpack_toolchain() | fbtenv_unpack_toolchain() | ||||||
| { | { | ||||||
|     echo "Unpacking toolchain:"; |     echo "Unpacking toolchain to '$FBT_TOOLCHAIN_PATH/toolchain':"; | ||||||
|     tar -xvf "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" -C "$FBT_TOOLCHAIN_PATH/toolchain" 2>&1 | fbtenv_show_unpack_percentage; |     tar -xvf "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" -C "$FBT_TOOLCHAIN_PATH/toolchain" 2>&1 | fbtenv_show_unpack_percentage; | ||||||
|     mkdir -p "$FBT_TOOLCHAIN_PATH/toolchain" || return 1; |     mkdir -p "$FBT_TOOLCHAIN_PATH/toolchain" || return 1; | ||||||
|     mv "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_DIR" "$TOOLCHAIN_ARCH_DIR" || return 1; |     mv "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_DIR" "$TOOLCHAIN_ARCH_DIR" || return 1; | ||||||
| @ -215,7 +221,7 @@ fbtenv_unpack_toolchain() | |||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fbtenv_clearing() | fbtenv_cleanup() | ||||||
| { | { | ||||||
|     printf "Cleaning up.."; |     printf "Cleaning up.."; | ||||||
|     if [ -n "${FBT_TOOLCHAIN_PATH:-""}" ]; then |     if [ -n "${FBT_TOOLCHAIN_PATH:-""}" ]; then | ||||||
| @ -270,14 +276,14 @@ fbtenv_download_toolchain() | |||||||
|     fbtenv_check_tar || return 1; |     fbtenv_check_tar || return 1; | ||||||
|     TOOLCHAIN_TAR="$(basename "$TOOLCHAIN_URL")"; |     TOOLCHAIN_TAR="$(basename "$TOOLCHAIN_URL")"; | ||||||
|     TOOLCHAIN_DIR="$(echo "$TOOLCHAIN_TAR" | sed "s/-$FBT_TOOLCHAIN_VERSION.tar.gz//g")"; |     TOOLCHAIN_DIR="$(echo "$TOOLCHAIN_TAR" | sed "s/-$FBT_TOOLCHAIN_VERSION.tar.gz//g")"; | ||||||
|     trap fbtenv_clearing 2;  # trap will be restored in fbtenv_clearing |     trap fbtenv_cleanup 2;  # trap will be restored in fbtenv_cleanup | ||||||
|     if ! fbtenv_check_downloaded_toolchain; then |     if ! fbtenv_check_downloaded_toolchain; then | ||||||
|         fbtenv_curl_wget_check || return 1; |         fbtenv_curl_wget_check || return 1; | ||||||
|         fbtenv_download_toolchain_tar || return 1; |         fbtenv_download_toolchain_tar || return 1; | ||||||
|     fi |     fi | ||||||
|     fbtenv_remove_old_tooclhain; |     fbtenv_remove_old_tooclhain; | ||||||
|     fbtenv_unpack_toolchain || return 1; |     fbtenv_unpack_toolchain || return 1; | ||||||
|     fbtenv_clearing; |     fbtenv_cleanup; | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -296,8 +302,8 @@ fbtenv_main() | |||||||
|         fbtenv_restore_env; |         fbtenv_restore_env; | ||||||
|         return 0; |         return 0; | ||||||
|     fi |     fi | ||||||
|     fbtenv_check_if_sourced_multiple_times;  # many source it's just a warning |     fbtenv_check_if_sourced_multiple_times; | ||||||
|     fbtenv_check_script_path || return 1; |     fbtenv_check_env_vars || return 1; | ||||||
|     fbtenv_check_download_toolchain || return 1; |     fbtenv_check_download_toolchain || return 1; | ||||||
|     fbtenv_set_shell_prompt; |     fbtenv_set_shell_prompt; | ||||||
|     fbtenv_print_version; |     fbtenv_print_version; | ||||||
|  | |||||||
							
								
								
									
										393
									
								
								scripts/ufbt/SConstruct
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										393
									
								
								scripts/ufbt/SConstruct
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,393 @@ | |||||||
|  | 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_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}), | ||||||
|  |     ], | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | if "update" in BUILD_TARGETS: | ||||||
|  |     SConscript( | ||||||
|  |         "update.scons", | ||||||
|  |         exports={"core_env": core_env}, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | if "purge" in BUILD_TARGETS: | ||||||
|  |     core_env.Execute(Delete(ufbt_state_dir)) | ||||||
|  |     print("uFBT state purged") | ||||||
|  |     Exit(0) | ||||||
|  | 
 | ||||||
|  | # 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 | ||||||
|  | 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", | ||||||
|  |         ("compilation_db", {"COMPILATIONDB_COMSTR": "\tCDB\t${TARGET}"}), | ||||||
|  |     ], | ||||||
|  |     FBT_FAP_DEBUG_ELF_ROOT=ufbt_state_dir.Dir("build"), | ||||||
|  |     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") | ||||||
|  | 
 | ||||||
|  | # 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_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")), | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | 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) | ||||||
|  | ] | ||||||
|  | for app in known_extapps: | ||||||
|  |     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 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"', | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | # 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") | ||||||
|  | app_template_dist = [] | ||||||
|  | for template_file in project_template_dir.Dir("app_template").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")), | ||||||
|  |     ], | ||||||
|  | ) | ||||||
|  | dist_env.Precious(app_template_dist) | ||||||
|  | dist_env.NoClean(app_template_dist) | ||||||
|  | dist_env.Alias("create", app_template_dist) | ||||||
							
								
								
									
										90
									
								
								scripts/ufbt/commandline.scons
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								scripts/ufbt/commandline.scons
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,90 @@ | |||||||
|  | AddOption( | ||||||
|  |     "--proxy-env", | ||||||
|  |     action="store", | ||||||
|  |     dest="proxy_env", | ||||||
|  |     default="", | ||||||
|  |     help="Comma-separated list of additional environment variables to pass to child SCons processes", | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | AddOption( | ||||||
|  |     "--channel", | ||||||
|  |     action="store", | ||||||
|  |     dest="sdk_channel", | ||||||
|  |     choices=["dev", "rc", "release"], | ||||||
|  |     default="", | ||||||
|  |     help="Release channel to use for SDK", | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | AddOption( | ||||||
|  |     "--branch", | ||||||
|  |     action="store", | ||||||
|  |     dest="sdk_branch", | ||||||
|  |     help="Custom main repo branch to use for SDK", | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | AddOption( | ||||||
|  |     "--hw-target", | ||||||
|  |     action="store", | ||||||
|  |     dest="sdk_target", | ||||||
|  |     help="SDK Hardware target", | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | vars = Variables("ufbt_options.py", ARGUMENTS) | ||||||
|  | 
 | ||||||
|  | vars.AddVariables( | ||||||
|  |     BoolVariable( | ||||||
|  |         "VERBOSE", | ||||||
|  |         help="Print full commands", | ||||||
|  |         default=False, | ||||||
|  |     ), | ||||||
|  |     BoolVariable( | ||||||
|  |         "FORCE", | ||||||
|  |         help="Force target action (for supported targets)", | ||||||
|  |         default=False, | ||||||
|  |     ), | ||||||
|  |     # These 2 are inherited from SDK | ||||||
|  |     # BoolVariable( | ||||||
|  |     #     "DEBUG", | ||||||
|  |     #     help="Enable debug build", | ||||||
|  |     #     default=True, | ||||||
|  |     # ), | ||||||
|  |     # BoolVariable( | ||||||
|  |     #     "COMPACT", | ||||||
|  |     #     help="Optimize for size", | ||||||
|  |     #     default=False, | ||||||
|  |     # ), | ||||||
|  |     PathVariable( | ||||||
|  |         "OTHER_ELF", | ||||||
|  |         help="Path to prebuilt ELF file to debug", | ||||||
|  |         validator=PathVariable.PathAccept, | ||||||
|  |         default="", | ||||||
|  |     ), | ||||||
|  |     ( | ||||||
|  |         "OPENOCD_OPTS", | ||||||
|  |         "Options to pass to OpenOCD", | ||||||
|  |         "", | ||||||
|  |     ), | ||||||
|  |     ( | ||||||
|  |         "BLACKMAGIC", | ||||||
|  |         "Blackmagic probe location", | ||||||
|  |         "auto", | ||||||
|  |     ), | ||||||
|  |     ( | ||||||
|  |         "OPENOCD_ADAPTER_SERIAL", | ||||||
|  |         "OpenOCD adapter serial number", | ||||||
|  |         "auto", | ||||||
|  |     ), | ||||||
|  |     ( | ||||||
|  |         "APPID", | ||||||
|  |         "Application id", | ||||||
|  |         "", | ||||||
|  |     ), | ||||||
|  |     PathVariable( | ||||||
|  |         "UFBT_APP_DIR", | ||||||
|  |         help="Application dir to work with", | ||||||
|  |         validator=PathVariable.PathIsDir, | ||||||
|  |         default="", | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | Return("vars") | ||||||
							
								
								
									
										191
									
								
								scripts/ufbt/project_template/.clang-format
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								scripts/ufbt/project_template/.clang-format
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,191 @@ | |||||||
|  | --- | ||||||
|  | Language:        Cpp | ||||||
|  | AccessModifierOffset: -4 | ||||||
|  | AlignAfterOpenBracket: AlwaysBreak | ||||||
|  | AlignArrayOfStructures: None | ||||||
|  | AlignConsecutiveMacros: None | ||||||
|  | AlignConsecutiveAssignments: None | ||||||
|  | AlignConsecutiveBitFields: None | ||||||
|  | AlignConsecutiveDeclarations: None | ||||||
|  | AlignEscapedNewlines: Left | ||||||
|  | AlignOperands:   Align | ||||||
|  | AlignTrailingComments: false | ||||||
|  | AllowAllArgumentsOnNextLine: true | ||||||
|  | AllowAllParametersOfDeclarationOnNextLine: false | ||||||
|  | AllowShortEnumsOnASingleLine: true | ||||||
|  | AllowShortBlocksOnASingleLine: Never | ||||||
|  | AllowShortCaseLabelsOnASingleLine: false | ||||||
|  | AllowShortFunctionsOnASingleLine: None | ||||||
|  | AllowShortLambdasOnASingleLine: All | ||||||
|  | AllowShortIfStatementsOnASingleLine: WithoutElse | ||||||
|  | AllowShortLoopsOnASingleLine: true | ||||||
|  | AlwaysBreakAfterDefinitionReturnType: None | ||||||
|  | AlwaysBreakAfterReturnType: None | ||||||
|  | AlwaysBreakBeforeMultilineStrings: false | ||||||
|  | AlwaysBreakTemplateDeclarations: Yes | ||||||
|  | AttributeMacros: | ||||||
|  |   - __capability | ||||||
|  | BinPackArguments: false | ||||||
|  | BinPackParameters: false | ||||||
|  | BraceWrapping: | ||||||
|  |   AfterCaseLabel:  false | ||||||
|  |   AfterClass:      false | ||||||
|  |   AfterControlStatement: Never | ||||||
|  |   AfterEnum:       false | ||||||
|  |   AfterFunction:   false | ||||||
|  |   AfterNamespace:  false | ||||||
|  |   AfterObjCDeclaration: false | ||||||
|  |   AfterStruct:     false | ||||||
|  |   AfterUnion:      false | ||||||
|  |   AfterExternBlock: false | ||||||
|  |   BeforeCatch:     false | ||||||
|  |   BeforeElse:      false | ||||||
|  |   BeforeLambdaBody: false | ||||||
|  |   BeforeWhile:     false | ||||||
|  |   IndentBraces:    false | ||||||
|  |   SplitEmptyFunction: true | ||||||
|  |   SplitEmptyRecord: true | ||||||
|  |   SplitEmptyNamespace: true | ||||||
|  | BreakBeforeBinaryOperators: None | ||||||
|  | BreakBeforeConceptDeclarations: true | ||||||
|  | BreakBeforeBraces: Attach | ||||||
|  | BreakBeforeInheritanceComma: false | ||||||
|  | BreakInheritanceList: BeforeColon | ||||||
|  | BreakBeforeTernaryOperators: false | ||||||
|  | BreakConstructorInitializersBeforeComma: false | ||||||
|  | BreakConstructorInitializers: BeforeComma | ||||||
|  | BreakAfterJavaFieldAnnotations: false | ||||||
|  | BreakStringLiterals: false | ||||||
|  | ColumnLimit:     99 | ||||||
|  | CommentPragmas:  '^ IWYU pragma:' | ||||||
|  | QualifierAlignment: Leave | ||||||
|  | CompactNamespaces: false | ||||||
|  | ConstructorInitializerIndentWidth: 4 | ||||||
|  | ContinuationIndentWidth: 4 | ||||||
|  | Cpp11BracedListStyle: true | ||||||
|  | DeriveLineEnding: true | ||||||
|  | DerivePointerAlignment: false | ||||||
|  | DisableFormat:   false | ||||||
|  | EmptyLineAfterAccessModifier: Never | ||||||
|  | EmptyLineBeforeAccessModifier: LogicalBlock | ||||||
|  | ExperimentalAutoDetectBinPacking: false | ||||||
|  | PackConstructorInitializers: BinPack | ||||||
|  | BasedOnStyle:    '' | ||||||
|  | ConstructorInitializerAllOnOneLineOrOnePerLine: false | ||||||
|  | AllowAllConstructorInitializersOnNextLine: true | ||||||
|  | FixNamespaceComments: false | ||||||
|  | ForEachMacros: | ||||||
|  |   - foreach | ||||||
|  |   - Q_FOREACH | ||||||
|  |   - BOOST_FOREACH | ||||||
|  | IfMacros: | ||||||
|  |   - KJ_IF_MAYBE | ||||||
|  | IncludeBlocks:   Preserve | ||||||
|  | IncludeCategories: | ||||||
|  |   - Regex:           '.*' | ||||||
|  |     Priority:        1 | ||||||
|  |     SortPriority:    0 | ||||||
|  |     CaseSensitive:   false | ||||||
|  |   - Regex:           '^(<|"(gtest|gmock|isl|json)/)' | ||||||
|  |     Priority:        3 | ||||||
|  |     SortPriority:    0 | ||||||
|  |     CaseSensitive:   false | ||||||
|  |   - Regex:           '.*' | ||||||
|  |     Priority:        1 | ||||||
|  |     SortPriority:    0 | ||||||
|  |     CaseSensitive:   false | ||||||
|  | IncludeIsMainRegex: '(Test)?$' | ||||||
|  | IncludeIsMainSourceRegex: '' | ||||||
|  | IndentAccessModifiers: false | ||||||
|  | IndentCaseLabels: false | ||||||
|  | IndentCaseBlocks: false | ||||||
|  | IndentGotoLabels: true | ||||||
|  | IndentPPDirectives: None | ||||||
|  | IndentExternBlock: AfterExternBlock | ||||||
|  | IndentRequires:  false | ||||||
|  | IndentWidth:     4 | ||||||
|  | IndentWrappedFunctionNames: true | ||||||
|  | InsertTrailingCommas: None | ||||||
|  | JavaScriptQuotes: Leave | ||||||
|  | JavaScriptWrapImports: true | ||||||
|  | KeepEmptyLinesAtTheStartOfBlocks: false | ||||||
|  | LambdaBodyIndentation: Signature | ||||||
|  | MacroBlockBegin: '' | ||||||
|  | MacroBlockEnd:   '' | ||||||
|  | MaxEmptyLinesToKeep: 1 | ||||||
|  | NamespaceIndentation: None | ||||||
|  | ObjCBinPackProtocolList: Auto | ||||||
|  | ObjCBlockIndentWidth: 4 | ||||||
|  | ObjCBreakBeforeNestedBlockParam: true | ||||||
|  | ObjCSpaceAfterProperty: true | ||||||
|  | ObjCSpaceBeforeProtocolList: true | ||||||
|  | PenaltyBreakAssignment: 10 | ||||||
|  | PenaltyBreakBeforeFirstCallParameter: 30 | ||||||
|  | PenaltyBreakComment: 10 | ||||||
|  | PenaltyBreakFirstLessLess: 0 | ||||||
|  | PenaltyBreakOpenParenthesis: 0 | ||||||
|  | PenaltyBreakString: 10 | ||||||
|  | PenaltyBreakTemplateDeclaration: 10 | ||||||
|  | PenaltyExcessCharacter: 100 | ||||||
|  | PenaltyReturnTypeOnItsOwnLine: 60 | ||||||
|  | PenaltyIndentedWhitespace: 0 | ||||||
|  | PointerAlignment: Left | ||||||
|  | PPIndentWidth:   -1 | ||||||
|  | ReferenceAlignment: Pointer | ||||||
|  | ReflowComments:  false | ||||||
|  | RemoveBracesLLVM: false | ||||||
|  | SeparateDefinitionBlocks: Leave | ||||||
|  | ShortNamespaceLines: 1 | ||||||
|  | SortIncludes:    Never | ||||||
|  | SortJavaStaticImport: Before | ||||||
|  | SortUsingDeclarations: false | ||||||
|  | SpaceAfterCStyleCast: false | ||||||
|  | SpaceAfterLogicalNot: false | ||||||
|  | SpaceAfterTemplateKeyword: true | ||||||
|  | SpaceBeforeAssignmentOperators: true | ||||||
|  | SpaceBeforeCaseColon: false | ||||||
|  | SpaceBeforeCpp11BracedList: false | ||||||
|  | SpaceBeforeCtorInitializerColon: true | ||||||
|  | SpaceBeforeInheritanceColon: true | ||||||
|  | SpaceBeforeParens: Never | ||||||
|  | SpaceBeforeParensOptions: | ||||||
|  |   AfterControlStatements: false | ||||||
|  |   AfterForeachMacros: false | ||||||
|  |   AfterFunctionDefinitionName: false | ||||||
|  |   AfterFunctionDeclarationName: false | ||||||
|  |   AfterIfMacros:   false | ||||||
|  |   AfterOverloadedOperator: false | ||||||
|  |   BeforeNonEmptyParentheses: false | ||||||
|  | SpaceAroundPointerQualifiers: Default | ||||||
|  | SpaceBeforeRangeBasedForLoopColon: true | ||||||
|  | SpaceInEmptyBlock: false | ||||||
|  | SpaceInEmptyParentheses: false | ||||||
|  | SpacesBeforeTrailingComments: 1 | ||||||
|  | SpacesInAngles:  Never | ||||||
|  | SpacesInConditionalStatement: false | ||||||
|  | SpacesInContainerLiterals: false | ||||||
|  | SpacesInCStyleCastParentheses: false | ||||||
|  | SpacesInLineCommentPrefix: | ||||||
|  |   Minimum:         1 | ||||||
|  |   Maximum:         -1 | ||||||
|  | SpacesInParentheses: false | ||||||
|  | SpacesInSquareBrackets: false | ||||||
|  | SpaceBeforeSquareBrackets: false | ||||||
|  | BitFieldColonSpacing: Both | ||||||
|  | Standard:        c++03 | ||||||
|  | StatementAttributeLikeMacros: | ||||||
|  |   - Q_EMIT | ||||||
|  | StatementMacros: | ||||||
|  |   - Q_UNUSED | ||||||
|  |   - QT_REQUIRE_VERSION | ||||||
|  | TabWidth:        4 | ||||||
|  | UseCRLF:         false | ||||||
|  | UseTab:          Never | ||||||
|  | WhitespaceSensitiveMacros: | ||||||
|  |   - STRINGIZE | ||||||
|  |   - PP_STRINGIZE | ||||||
|  |   - BOOST_PP_STRINGIZE | ||||||
|  |   - NS_SWIFT_NAME | ||||||
|  |   - CF_SWIFT_NAME | ||||||
|  | ... | ||||||
|  | 
 | ||||||
							
								
								
									
										13
									
								
								scripts/ufbt/project_template/.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								scripts/ufbt/project_template/.editorconfig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | root = true | ||||||
|  | 
 | ||||||
|  | [*] | ||||||
|  | end_of_line = lf | ||||||
|  | insert_final_newline = true | ||||||
|  | charset = utf-8 | ||||||
|  | 
 | ||||||
|  | [*.{cpp,h,c,py,sh}] | ||||||
|  | indent_style = space | ||||||
|  | indent_size = 4 | ||||||
|  | 
 | ||||||
|  | [{Makefile,*.mk}] | ||||||
|  | indent_size = tab | ||||||
							
								
								
									
										4
									
								
								scripts/ufbt/project_template/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								scripts/ufbt/project_template/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | dist/* | ||||||
|  | .vscode | ||||||
|  | .clang-format | ||||||
|  | .editorconfig | ||||||
							
								
								
									
										14
									
								
								scripts/ufbt/project_template/.vscode/c_cpp_properties.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								scripts/ufbt/project_template/.vscode/c_cpp_properties.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | { | ||||||
|  |     "configurations": [ | ||||||
|  |         { | ||||||
|  |             "name": "main", | ||||||
|  |             "compilerPath": "@UFBT_TOOLCHAIN_GCC@", | ||||||
|  |             "intelliSenseMode": "gcc-arm", | ||||||
|  |             "compileCommands": "${workspaceFolder}/.vscode/compile_commands.json", | ||||||
|  |             "configurationProvider": "ms-vscode.cpptools", | ||||||
|  |             "cStandard": "gnu17", | ||||||
|  |             "cppStandard": "c++17" | ||||||
|  |         }, | ||||||
|  |     ], | ||||||
|  |     "version": 4 | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								scripts/ufbt/project_template/.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								scripts/ufbt/project_template/.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | { | ||||||
|  | 	// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. | ||||||
|  | 	// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp | ||||||
|  | 	// List of extensions which should be recommended for users of this workspace. | ||||||
|  | 	"recommendations": [ | ||||||
|  | 		"ms-python.black-formatter", | ||||||
|  | 		"ms-vscode.cpptools", | ||||||
|  | 		"amiralizadeh9480.cpp-helper", | ||||||
|  | 		"marus25.cortex-debug", | ||||||
|  | 		"zxh404.vscode-proto3", | ||||||
|  | 		"augustocdias.tasks-shell-input" | ||||||
|  | 	], | ||||||
|  | 	// List of extensions recommended by VS Code that should not be recommended for users of this workspace. | ||||||
|  | 	"unwantedRecommendations": [ | ||||||
|  | 		"twxs.cmake", | ||||||
|  | 		"ms-vscode.cmake-tools" | ||||||
|  | 	] | ||||||
|  | } | ||||||
							
								
								
									
										98
									
								
								scripts/ufbt/project_template/.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								scripts/ufbt/project_template/.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,98 @@ | |||||||
|  | { | ||||||
|  |     // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||||||
|  |     "version": "0.2.0", | ||||||
|  |     "inputs": [ | ||||||
|  |         // { | ||||||
|  |         //     "id": "BLACKMAGIC", | ||||||
|  |         //     "type": "command", | ||||||
|  |         //     "command": "shellCommand.execute", | ||||||
|  |         //     "args": { | ||||||
|  |         //         "useSingleResult": true, | ||||||
|  |         //         "env": { | ||||||
|  |         //             "PATH": "${workspaceFolder};${env:PATH}" | ||||||
|  |         //         }, | ||||||
|  |         //         "command": "./fbt get_blackmagic", | ||||||
|  |         //         "description": "Get Blackmagic device", | ||||||
|  |         //     } | ||||||
|  |         // }, | ||||||
|  |     ], | ||||||
|  |     "configurations": [ | ||||||
|  |         { | ||||||
|  |             "name": "Attach FW (ST-Link)", | ||||||
|  |             "cwd": "${workspaceFolder}", | ||||||
|  |             "executable": "@UFBT_FIRMWARE_ELF@", | ||||||
|  |             "request": "attach", | ||||||
|  |             "type": "cortex-debug", | ||||||
|  |             "servertype": "openocd", | ||||||
|  |             "device": "stlink", | ||||||
|  |             "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", | ||||||
|  |             "rtos": "FreeRTOS", | ||||||
|  |             "configFiles": [ | ||||||
|  |                 "interface/stlink.cfg", | ||||||
|  |                 "@UFBT_DEBUG_DIR@/stm32wbx.cfg" | ||||||
|  |             ], | ||||||
|  |             "postAttachCommands": [ | ||||||
|  |                 "source @UFBT_DEBUG_DIR@/flipperapps.py", | ||||||
|  |                 "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" | ||||||
|  |             ], | ||||||
|  |             // "showDevDebugOutput": "raw", | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "name": "Attach FW (DAP)", | ||||||
|  |             "cwd": "${workspaceFolder}", | ||||||
|  |             "executable": "@UFBT_FIRMWARE_ELF@", | ||||||
|  |             "request": "attach", | ||||||
|  |             "type": "cortex-debug", | ||||||
|  |             "servertype": "openocd", | ||||||
|  |             "device": "cmsis-dap", | ||||||
|  |             "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", | ||||||
|  |             "rtos": "FreeRTOS", | ||||||
|  |             "configFiles": [ | ||||||
|  |                 "interface/cmsis-dap.cfg", | ||||||
|  |                 "@UFBT_DEBUG_DIR@/stm32wbx.cfg" | ||||||
|  |             ], | ||||||
|  |             "postAttachCommands": [ | ||||||
|  |                 "source @UFBT_DEBUG_DIR@/flipperapps.py", | ||||||
|  |                 "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" | ||||||
|  |             ], | ||||||
|  |             // "showDevDebugOutput": "raw", | ||||||
|  |         }, | ||||||
|  |         // { | ||||||
|  |         //     "name": "Attach FW (blackmagic)", | ||||||
|  |         //     "cwd": "${workspaceFolder}", | ||||||
|  |         //     "executable": "@UFBT_FIRMWARE_ELF@", | ||||||
|  |         //     "request": "attach", | ||||||
|  |         //     "type": "cortex-debug", | ||||||
|  |         //     "servertype": "external", | ||||||
|  |         //     "gdbTarget": "${input:BLACKMAGIC}", | ||||||
|  |         //     "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", | ||||||
|  |         //     "rtos": "FreeRTOS", | ||||||
|  |         //     "postAttachCommands": [ | ||||||
|  |         //         "monitor swdp_scan", | ||||||
|  |         //         "attach 1", | ||||||
|  |         //         "set confirm off", | ||||||
|  |         //         "set mem inaccessible-by-default off", | ||||||
|  |         //         "source @UFBT_DEBUG_DIR@/flipperapps.py", | ||||||
|  |         //         "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" | ||||||
|  |         //     ] | ||||||
|  |         //     // "showDevDebugOutput": "raw", | ||||||
|  |         // }, | ||||||
|  |         { | ||||||
|  |             "name": "Attach FW (JLink)", | ||||||
|  |             "cwd": "${workspaceFolder}", | ||||||
|  |             "executable": "@UFBT_FIRMWARE_ELF@", | ||||||
|  |             "request": "attach", | ||||||
|  |             "type": "cortex-debug", | ||||||
|  |             "servertype": "jlink", | ||||||
|  |             "interface": "swd", | ||||||
|  |             "device": "STM32WB55RG", | ||||||
|  |             "svdFile": "@UFBT_DEBUG_DIR@/STM32WB55_CM4.svd", | ||||||
|  |             "rtos": "FreeRTOS", | ||||||
|  |             "postAttachCommands": [ | ||||||
|  |                 "source @UFBT_DEBUG_DIR@/flipperapps.py", | ||||||
|  |                 "fap-set-debug-elf-root @UFBT_DEBUG_ELF_DIR@" | ||||||
|  |             ] | ||||||
|  |             // "showDevDebugOutput": "raw", | ||||||
|  |         }, | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								scripts/ufbt/project_template/.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								scripts/ufbt/project_template/.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | { | ||||||
|  |     "cortex-debug.enableTelemetry": false, | ||||||
|  |     "cortex-debug.variableUseNaturalFormat": false, | ||||||
|  |     "cortex-debug.showRTOS": true, | ||||||
|  |     "cortex-debug.armToolchainPath": "@UFBT_TOOLCHAIN_ARM_TOOLCHAIN_DIR@", | ||||||
|  |     "cortex-debug.openocdPath": "@UFBT_TOOLCHAIN_OPENOCD@", | ||||||
|  |     "cortex-debug.gdbPath": "@UFBT_TOOLCHAIN_GDB_PY@", | ||||||
|  |     "editor.formatOnSave": true, | ||||||
|  |     "files.associations": { | ||||||
|  |         "*.scons": "python", | ||||||
|  |         "SConscript": "python", | ||||||
|  |         "SConstruct": "python", | ||||||
|  |         "*.fam": "python" | ||||||
|  |     }, | ||||||
|  |     "cortex-debug.registerUseNaturalFormat": false, | ||||||
|  |     "python.analysis.typeCheckingMode": "off", | ||||||
|  |     "[python]": { | ||||||
|  |         "editor.defaultFormatter": "ms-python.black-formatter" | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										54
									
								
								scripts/ufbt/project_template/.vscode/tasks.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								scripts/ufbt/project_template/.vscode/tasks.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | |||||||
|  | { | ||||||
|  |     // See https://go.microsoft.com/fwlink/?LinkId=733558 | ||||||
|  |     // for the documentation about the tasks.json format | ||||||
|  |     "version": "2.0.0", | ||||||
|  |     "options": { | ||||||
|  |         "env": { | ||||||
|  |             "PATH": "${workspaceFolder}@UFBT_VSCODE_PATH_SEP@${env:PATH}@UFBT_VSCODE_PATH_SEP@@UFBT_ROOT_DIR@" | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     "tasks": [ | ||||||
|  |         { | ||||||
|  |             "label": "Launch App on Flipper", | ||||||
|  |             "group": "build", | ||||||
|  |             "type": "shell", | ||||||
|  |             "command": "ufbt launch" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "label": "Build", | ||||||
|  |             "group": "build", | ||||||
|  |             "type": "shell", | ||||||
|  |             "command": "ufbt" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "label": "Flash FW (ST-Link)", | ||||||
|  |             "group": "build", | ||||||
|  |             "type": "shell", | ||||||
|  |             "command": "ufbt FORCE=1 flash" | ||||||
|  |         }, | ||||||
|  |         // { | ||||||
|  |         //     "label": "[NOTIMPL] Flash FW (blackmagic)", | ||||||
|  |         //     "group": "build", | ||||||
|  |         //     "type": "shell", | ||||||
|  |         //     "command": "ufbt flash_blackmagic" | ||||||
|  |         // }, | ||||||
|  |         // { | ||||||
|  |         //     "label": "[NOTIMPL] Flash FW (JLink)", | ||||||
|  |         //     "group": "build", | ||||||
|  |         //     "type": "shell", | ||||||
|  |         //     "command": "ufbt FORCE=1 jflash" | ||||||
|  |         // }, | ||||||
|  |         { | ||||||
|  |             "label": "Flash FW (USB, with resources)", | ||||||
|  |             "group": "build", | ||||||
|  |             "type": "shell", | ||||||
|  |             "command": "ufbt FORCE=1 flash_usb" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "label": "Update uFBT SDK", | ||||||
|  |             "group": "build", | ||||||
|  |             "type": "shell", | ||||||
|  |             "command": "ufbt update" | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								scripts/ufbt/project_template/app_template/${FBT_APPID}.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								scripts/ufbt/project_template/app_template/${FBT_APPID}.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | /* generated by fbt from .png files in images folder */ | ||||||
|  | #include <@FBT_APPID@_icons.h> | ||||||
|  | 
 | ||||||
|  | int32_t @FBT_APPID@_app(void* p) { | ||||||
|  |     UNUSED(p); | ||||||
|  |     FURI_LOG_I("TEST", "Hello world"); | ||||||
|  |     FURI_LOG_I("TEST", "I'm @FBT_APPID@!"); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								scripts/ufbt/project_template/app_template/${FBT_APPID}.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								scripts/ufbt/project_template/app_template/${FBT_APPID}.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 220 B | 
							
								
								
									
										17
									
								
								scripts/ufbt/project_template/app_template/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								scripts/ufbt/project_template/app_template/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | # For details & more options, see documentation/AppManifests.md in firmware repo | ||||||
|  | 
 | ||||||
|  | App( | ||||||
|  |     appid="@FBT_APPID@",  # Must be unique | ||||||
|  |     name="App @FBT_APPID@",  # Displayed in menus | ||||||
|  |     apptype=FlipperAppType.EXTERNAL, | ||||||
|  |     entry_point="@FBT_APPID@_app", | ||||||
|  |     stack_size=2 * 1024, | ||||||
|  |     fap_category="Misc", | ||||||
|  |     # Optional values | ||||||
|  |     # fap_version=(0, 1),  # (major, minor) | ||||||
|  |     fap_icon="@FBT_APPID@.png",  # 10x10 1-bit PNG | ||||||
|  |     # fap_description="A simple app", | ||||||
|  |     # fap_author="J. Doe", | ||||||
|  |     # fap_weburl="https://github.com/user/@FBT_APPID@", | ||||||
|  |     fap_icon_assets="images",  # Image assets to compile for this application | ||||||
|  | ) | ||||||
							
								
								
									
										36
									
								
								scripts/ufbt/site_init.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								scripts/ufbt/site_init.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | |||||||
|  | from SCons.Script import GetBuildFailures | ||||||
|  | import SCons.Errors | ||||||
|  | 
 | ||||||
|  | import atexit | ||||||
|  | from ansi.color import fg, fx | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def bf_to_str(bf): | ||||||
|  |     """Convert an element of GetBuildFailures() to a string | ||||||
|  |     in a useful way.""" | ||||||
|  | 
 | ||||||
|  |     if bf is None:  # unknown targets product None in list | ||||||
|  |         return "(unknown tgt)" | ||||||
|  |     elif isinstance(bf, SCons.Errors.StopError): | ||||||
|  |         return fg.yellow(str(bf)) | ||||||
|  |     elif bf.node: | ||||||
|  |         return fg.yellow(str(bf.node)) + ": " + bf.errstr | ||||||
|  |     elif bf.filename: | ||||||
|  |         return fg.yellow(bf.filename) + ": " + bf.errstr | ||||||
|  |     return fg.yellow("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([bf_to_str(x) for x in bf if x is not None]) | ||||||
|  |         print() | ||||||
|  |         print(fg.brightred(fx.bold("*" * 10 + " FBT ERRORS " + "*" * 10))) | ||||||
|  |         print(failures_message) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | atexit.register(display_build_status) | ||||||
							
								
								
									
										53
									
								
								scripts/ufbt/site_tools/ufbt_help.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								scripts/ufbt/site_tools/ufbt_help.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | |||||||
|  | targets_help = """Configuration variables: | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | tail_help = """ | ||||||
|  | 
 | ||||||
|  | TASKS: | ||||||
|  |     (* - not supported yet) | ||||||
|  | 
 | ||||||
|  |     launch: | ||||||
|  |         Upload and start application over USB | ||||||
|  |     vscode_dist: | ||||||
|  |         Configure application in current directory for development in VSCode. | ||||||
|  |     create: | ||||||
|  |         Copy application template to current directory. Set APPID=myapp to create an app with id 'myapp'. | ||||||
|  | 
 | ||||||
|  | Building: | ||||||
|  |     faps: | ||||||
|  |         Build all FAP apps | ||||||
|  |     fap_{APPID}, launch 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 | ||||||
|  |     lint: | ||||||
|  |         run linter for C code | ||||||
|  |     format: | ||||||
|  |         reformat C code | ||||||
|  | 
 | ||||||
|  | How to create a new application: | ||||||
|  |     1. Create a new directory for your application and cd into it. | ||||||
|  |     2. Run `ufbt vscode_dist create APPID=myapp` | ||||||
|  |     3. In VSCode, open the folder and start editing. | ||||||
|  |     4. Run `ufbt launch` to build and upload your application. | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 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 | ||||||
							
								
								
									
										117
									
								
								scripts/ufbt/site_tools/ufbt_state.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								scripts/ufbt/site_tools/ufbt_state.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | |||||||
|  | from SCons.Errors import StopError | ||||||
|  | from SCons.Warnings import warn, WarningOnByDefault | ||||||
|  | 
 | ||||||
|  | import json | ||||||
|  | import os | ||||||
|  | import sys | ||||||
|  | import pathlib | ||||||
|  | from functools import reduce | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def _load_sdk_data(sdk_root): | ||||||
|  |     split_vars = { | ||||||
|  |         "cc_args", | ||||||
|  |         "cpp_args", | ||||||
|  |         "linker_args", | ||||||
|  |         "linker_libs", | ||||||
|  |     } | ||||||
|  |     subst_vars = split_vars | { | ||||||
|  |         "sdk_symbols", | ||||||
|  |     } | ||||||
|  |     sdk_data = {} | ||||||
|  |     with open(os.path.join(sdk_root, "sdk.opts")) as f: | ||||||
|  |         sdk_json_data = json.load(f) | ||||||
|  |         replacements = { | ||||||
|  |             sdk_json_data["app_ep_subst"]: "${APP_ENTRY}", | ||||||
|  |             sdk_json_data["sdk_path_subst"]: sdk_root.replace("\\", "/"), | ||||||
|  |             sdk_json_data["map_file_subst"]: "${TARGET}", | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         def do_value_substs(src_value): | ||||||
|  |             if isinstance(src_value, str): | ||||||
|  |                 return reduce( | ||||||
|  |                     lambda acc, kv: acc.replace(*kv), replacements.items(), src_value | ||||||
|  |                 ) | ||||||
|  |             elif isinstance(src_value, list): | ||||||
|  |                 return [do_value_substs(v) for v in src_value] | ||||||
|  |             else: | ||||||
|  |                 return src_value | ||||||
|  | 
 | ||||||
|  |         for key, value in sdk_json_data.items(): | ||||||
|  |             if key in split_vars: | ||||||
|  |                 value = value.split() | ||||||
|  |             if key in subst_vars: | ||||||
|  |                 value = do_value_substs(value) | ||||||
|  |             sdk_data[key] = value | ||||||
|  | 
 | ||||||
|  |     return sdk_data | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def _load_state_file(state_dir_node, filename: str) -> dict: | ||||||
|  |     state_path = os.path.join(state_dir_node.abspath, filename) | ||||||
|  |     if not os.path.exists(state_path): | ||||||
|  |         raise StopError(f"State file {state_path} not found") | ||||||
|  | 
 | ||||||
|  |     with open(state_path, "r") as f: | ||||||
|  |         return json.load(f) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def generate(env, **kw): | ||||||
|  |     sdk_current_sdk_dir_node = env["UFBT_CURRENT_SDK_DIR"] | ||||||
|  | 
 | ||||||
|  |     sdk_components_filename = kw.get("SDK_COMPONENTS", "components.json") | ||||||
|  |     ufbt_state_filename = kw.get("UFBT_STATE", "ufbt_state.json") | ||||||
|  | 
 | ||||||
|  |     sdk_state = _load_state_file(sdk_current_sdk_dir_node, sdk_components_filename) | ||||||
|  |     ufbt_state = _load_state_file(sdk_current_sdk_dir_node, ufbt_state_filename) | ||||||
|  | 
 | ||||||
|  |     if not (sdk_components := sdk_state.get("components", {})): | ||||||
|  |         raise StopError("SDK state file doesn't contain components data") | ||||||
|  | 
 | ||||||
|  |     sdk_data = _load_sdk_data( | ||||||
|  |         sdk_current_sdk_dir_node.Dir(sdk_components["sdk_headers.dir"]).abspath | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     if not sdk_state["meta"]["hw_target"].endswith(sdk_data["hardware"]): | ||||||
|  |         raise StopError("SDK state file doesn't match hardware target") | ||||||
|  | 
 | ||||||
|  |     if sdk_state["meta"]["version"] != ufbt_state["version"]: | ||||||
|  |         warn( | ||||||
|  |             WarningOnByDefault, | ||||||
|  |             f"Version mismatch: SDK state vs uFBT: {sdk_state['meta']['version']} vs {ufbt_state['version']}", | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     scripts_dir = sdk_current_sdk_dir_node.Dir(sdk_components["scripts.dir"]) | ||||||
|  |     env.SetDefault( | ||||||
|  |         # Paths | ||||||
|  |         SDK_DEFINITION=env.File(sdk_data["sdk_symbols"]), | ||||||
|  |         FBT_DEBUG_DIR=pathlib.Path( | ||||||
|  |             sdk_current_sdk_dir_node.Dir(sdk_components["debug.dir"]).abspath | ||||||
|  |         ).as_posix(), | ||||||
|  |         FBT_SCRIPT_DIR=scripts_dir, | ||||||
|  |         LIBPATH=sdk_current_sdk_dir_node.Dir(sdk_components["lib.dir"]), | ||||||
|  |         FW_ELF=sdk_current_sdk_dir_node.File(sdk_components["firmware.elf"]), | ||||||
|  |         FW_BIN=sdk_current_sdk_dir_node.File(sdk_components["full.bin"]), | ||||||
|  |         UPDATE_BUNDLE_DIR=sdk_current_sdk_dir_node.Dir(sdk_components["update.dir"]), | ||||||
|  |         SVD_FILE="${FBT_DEBUG_DIR}/STM32WB55_CM4.svd", | ||||||
|  |         # Build variables | ||||||
|  |         ROOT_DIR=env.Dir("#"), | ||||||
|  |         FIRMWARE_BUILD_CFG="firmware", | ||||||
|  |         TARGET_HW=int(sdk_data["hardware"]), | ||||||
|  |         CFLAGS_APP=sdk_data["cc_args"], | ||||||
|  |         CXXFLAGS_APP=sdk_data["cpp_args"], | ||||||
|  |         LINKFLAGS_APP=sdk_data["linker_args"], | ||||||
|  |         LIBS=sdk_data["linker_libs"], | ||||||
|  |         # ufbt state | ||||||
|  |         # UFBT_STATE_DIR=ufbt_state_dir_node, | ||||||
|  |         # UFBT_CURRENT_SDK_DIR=sdk_current_sdk_dir_node, | ||||||
|  |         UFBT_STATE=ufbt_state, | ||||||
|  |         UFBT_BOOTSTRAP_SCRIPT="${UFBT_SCRIPT_DIR}/bootstrap.py", | ||||||
|  |         UFBT_SCRIPT_ROOT=scripts_dir.Dir("ufbt"), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     sys.path.insert(0, env["FBT_SCRIPT_DIR"].abspath) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def exists(env): | ||||||
|  |     return True | ||||||
							
								
								
									
										37
									
								
								scripts/ufbt/update.scons
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								scripts/ufbt/update.scons
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | |||||||
|  | from SCons.Errors import StopError | ||||||
|  | 
 | ||||||
|  | Import("core_env") | ||||||
|  | 
 | ||||||
|  | update_env = core_env.Clone( | ||||||
|  |     toolpath=[core_env["FBT_SCRIPT_DIR"].Dir("fbt_tools")], | ||||||
|  |     tools=["python3"], | ||||||
|  | ) | ||||||
|  | print("Updating SDK...") | ||||||
|  | ufbt_state = update_env["UFBT_STATE"] | ||||||
|  | 
 | ||||||
|  | update_args = [ | ||||||
|  |     "--ufbt-dir", | ||||||
|  |     f'"{update_env["UFBT_STATE_DIR"]}"', | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | if branch_name := GetOption("sdk_branch"): | ||||||
|  |     update_args.extend(["--branch", branch_name]) | ||||||
|  | elif channel_name := GetOption("sdk_channel"): | ||||||
|  |     update_args.extend(["--channel", channel_name]) | ||||||
|  | elif branch_name := ufbt_state.get("branch", None): | ||||||
|  |     update_args.extend(["--branch", branch_name]) | ||||||
|  | elif channel_name := ufbt_state.get("channel", None): | ||||||
|  |     update_args.extend(["--channel", channel_name]) | ||||||
|  | else: | ||||||
|  |     raise StopError("No branch or channel specified for SDK update") | ||||||
|  | 
 | ||||||
|  | if hw_target := GetOption("sdk_target"): | ||||||
|  |     update_args.extend(["--hw-target", hw_target]) | ||||||
|  | else: | ||||||
|  |     update_args.extend(["--hw-target", ufbt_state["hw_target"]]) | ||||||
|  | 
 | ||||||
|  | update_env.Replace(UPDATE_ARGS=update_args) | ||||||
|  | result = update_env.Execute( | ||||||
|  |     update_env.subst('$PYTHON3 "$UFBT_BOOTSTRAP_SCRIPT" $UPDATE_ARGS'), | ||||||
|  | ) | ||||||
|  | Exit(result) | ||||||
| @ -112,86 +112,47 @@ Alias( | |||||||
| 
 | 
 | ||||||
| extapps.resources_dist = appenv.FapDist(appenv["RESOURCES_ROOT"], []) | extapps.resources_dist = appenv.FapDist(appenv["RESOURCES_ROOT"], []) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| if appsrc := appenv.subst("$APPSRC"): | if appsrc := appenv.subst("$APPSRC"): | ||||||
|     deploy_sources, flipp_dist_paths, validators = [], [], [] |     appenv.AddAppLaunchTarget(appsrc, "launch_app") | ||||||
|     run_script_extra_ars = "" |  | ||||||
| 
 | 
 | ||||||
|     def _add_dist_targets(app_artifacts): |  | ||||||
|         validators.append(app_artifacts.validator) |  | ||||||
|         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 |  | ||||||
| 
 |  | ||||||
|     def _add_host_app_to_targets(host_app): |  | ||||||
|         artifacts_app_to_run = appenv["EXT_APPS"].get(host_app.appid, None) |  | ||||||
|         _add_dist_targets(artifacts_app_to_run) |  | ||||||
|         for plugin in host_app._plugins: |  | ||||||
|             _add_dist_targets(appenv["EXT_APPS"].get(plugin.appid, None)) |  | ||||||
| 
 |  | ||||||
|     artifacts_app_to_run = appenv.GetExtAppByIdOrPath(appsrc) |  | ||||||
|     if artifacts_app_to_run.app.apptype == FlipperAppType.PLUGIN: |  | ||||||
|         # We deploy host app instead |  | ||||||
|         host_app = appenv["APPMGR"].get(artifacts_app_to_run.app.requires[0]) |  | ||||||
| 
 |  | ||||||
|         if host_app: |  | ||||||
|             if host_app.apptype == FlipperAppType.EXTERNAL: |  | ||||||
|                 _add_host_app_to_targets(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) |  | ||||||
|         else: |  | ||||||
|             raise UserError("Host app is unknown") |  | ||||||
|     else: |  | ||||||
|         _add_host_app_to_targets(artifacts_app_to_run.app) |  | ||||||
| 
 |  | ||||||
|     # print(deploy_sources, flipp_dist_paths) |  | ||||||
|     appenv.PhonyTarget( |  | ||||||
|         "launch_app", |  | ||||||
|         '${PYTHON3} "${APP_RUN_SCRIPT}" ${EXTRA_ARGS} -s ${SOURCES} -t ${FLIPPER_FILE_TARGETS}', |  | ||||||
|         source=deploy_sources, |  | ||||||
|         FLIPPER_FILE_TARGETS=flipp_dist_paths, |  | ||||||
|         EXTRA_ARGS=run_script_extra_ars, |  | ||||||
|     ) |  | ||||||
|     appenv.Alias("launch_app", validators) |  | ||||||
| 
 | 
 | ||||||
| # SDK management | # SDK management | ||||||
| 
 | 
 | ||||||
| sdk_origin_path = "${BUILD_DIR}/sdk_origin" | amalgamated_api = "${BUILD_DIR}/sdk_origin" | ||||||
| sdk_source = appenv.SDKPrebuilder( | sdk_source = appenv.ApiAmalgamator( | ||||||
|     sdk_origin_path, |     amalgamated_api, | ||||||
|     # Deps on root SDK headers and generated files |     # Deps on root SDK headers and generated files | ||||||
|     (appenv["SDK_HEADERS"], appenv["FW_ASSETS_HEADERS"]), |     (appenv["SDK_HEADERS"], appenv["FW_ASSETS_HEADERS"]), | ||||||
| ) | ) | ||||||
| # Extra deps on headers included in deeper levels | # Extra deps on headers included in deeper levels | ||||||
| # Available on second and subsequent builds | # Available on second and subsequent builds | ||||||
| Depends(sdk_source, appenv.ProcessSdkDepends(f"{sdk_origin_path}.d")) | Depends(sdk_source, appenv.ProcessSdkDepends(f"{amalgamated_api}.d")) | ||||||
| 
 | 
 | ||||||
| appenv["SDK_DIR"] = appenv.Dir("${BUILD_DIR}/sdk") | appenv["SDK_DIR"] = appenv.Dir("${BUILD_DIR}/sdk_headers") | ||||||
| sdk_tree = appenv.SDKTree(appenv["SDK_DIR"], sdk_origin_path) | sdk_header_tree = appenv.SDKHeaderTreeExtractor(appenv["SDK_DIR"], amalgamated_api) | ||||||
| # AlwaysBuild(sdk_tree) | # AlwaysBuild(sdk_tree) | ||||||
| Alias("sdk_tree", sdk_tree) | Alias("sdk_tree", sdk_header_tree) | ||||||
| extapps.sdk_tree = sdk_tree | extapps.sdk_tree = sdk_header_tree | ||||||
| 
 | 
 | ||||||
| sdk_apicheck = appenv.SDKSymUpdater(appenv["SDK_DEFINITION"], sdk_origin_path) | api_check = appenv.ApiTableValidator(appenv["SDK_DEFINITION"], amalgamated_api) | ||||||
| Precious(sdk_apicheck) | Precious(api_check) | ||||||
| NoClean(sdk_apicheck) | NoClean(api_check) | ||||||
| AlwaysBuild(sdk_apicheck) | AlwaysBuild(api_check) | ||||||
| Alias("sdk_check", sdk_apicheck) | Alias("api_check", api_check) | ||||||
| 
 | 
 | ||||||
| sdk_apisyms = appenv.SDKSymGenerator( | firmware_apitable = appenv.ApiSymbolTable( | ||||||
|     "${BUILD_DIR}/assets/compiled/symbols.h", appenv["SDK_DEFINITION"] |     "${BUILD_DIR}/assets/compiled/firmware_api_table.h", appenv["SDK_DEFINITION"] | ||||||
| ) | ) | ||||||
| Alias("api_syms", sdk_apisyms) | Alias("api_table", firmware_apitable) | ||||||
| ENV.Replace( | ENV.Replace( | ||||||
|     SDK_APISYMS=sdk_apisyms, |     FW_API_TABLE=firmware_apitable, | ||||||
|     _APP_ICONS=appenv["_APP_ICONS"], |     _APP_ICONS=appenv["_APP_ICONS"], | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if appenv["FORCE"]: | if appenv["FORCE"]: | ||||||
|     appenv.AlwaysBuild(sdk_source, sdk_tree, sdk_apicheck, sdk_apisyms) |     appenv.AlwaysBuild(sdk_source, sdk_header_tree, api_check, firmware_apitable) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Return("extapps") | Return("extapps") | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 hedger
						hedger