[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> | ||||
| 
 | ||||
| /* Generated table */ | ||||
| #include <symbols.h> | ||||
| #include <firmware_api_table.h> | ||||
| 
 | ||||
| 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. | ||||
| 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 | ||||
| 
 | ||||
| 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, | ||||
| ) | ||||
| 
 | ||||
| @ -241,7 +241,7 @@ Depends( | ||||
|     [ | ||||
|         fwenv["FW_VERSION_JSON"], | ||||
|         fwenv["FW_ASSETS_HEADERS"], | ||||
|         fwenv["SDK_APISYMS"], | ||||
|         fwenv["FW_API_TABLE"], | ||||
|         fwenv["_APP_ICONS"], | ||||
|     ], | ||||
| ) | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| from SCons.Builder import Builder | ||||
| from SCons.Action import Action | ||||
| from SCons.Errors import SConsEnvironmentError | ||||
| from SCons.Errors import StopError | ||||
| 
 | ||||
| import os | ||||
| import subprocess | ||||
| @ -90,7 +90,7 @@ def proto_ver_generator(target, source, env): | ||||
|             source_dir=src_dir, | ||||
|         ) | ||||
|     except (subprocess.CalledProcessError, EnvironmentError) as e: | ||||
|         raise SConsEnvironmentError("Git: describe failed") | ||||
|         raise StopError("Git: describe failed") | ||||
| 
 | ||||
|     git_major, git_minor = git_describe.split(".") | ||||
|     version_file_data = ( | ||||
|  | ||||
| @ -21,6 +21,10 @@ from fbt.sdk.cache import SdkCache | ||||
| from fbt.util import extract_abs_dir_path | ||||
| 
 | ||||
| 
 | ||||
| _FAP_META_SECTION = ".fapmeta" | ||||
| _FAP_FILEASSETS_SECTION = ".fapassets" | ||||
| 
 | ||||
| 
 | ||||
| @dataclass | ||||
| class FlipperExternalAppInfo: | ||||
|     app: FlipperApplication | ||||
| @ -234,6 +238,8 @@ def BuildAppElf(env, app): | ||||
| 
 | ||||
| 
 | ||||
| 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) | ||||
| 
 | ||||
|     if not sdk_cache.is_buildable(): | ||||
| @ -242,8 +248,7 @@ def prepare_app_metadata(target, source, env): | ||||
|         ) | ||||
| 
 | ||||
|     app = env["APP"] | ||||
|     meta_file_name = source[0].path + ".meta" | ||||
|     with open(meta_file_name, "wb") as f: | ||||
|     with open(metadata_node.abspath, "wb") as f: | ||||
|         f.write( | ||||
|             assemble_manifest_data( | ||||
|                 app_manifest=app, | ||||
| @ -337,24 +342,26 @@ def embed_app_metadata_emitter(target, source, env): | ||||
|     if app.apptype == FlipperAppType.PLUGIN: | ||||
|         target[0].name = target[0].name.replace(".fap", ".fal") | ||||
| 
 | ||||
|     meta_file_name = source[0].path + ".meta" | ||||
|     target.append("#" + meta_file_name) | ||||
|     target.append(env.File(source[0].abspath + _FAP_META_SECTION)) | ||||
| 
 | ||||
|     if app.fap_file_assets: | ||||
|         files_section = source[0].path + ".files.section" | ||||
|         target.append("#" + files_section) | ||||
|         target.append(env.File(source[0].abspath + _FAP_FILEASSETS_SECTION)) | ||||
| 
 | ||||
|     return (target, source) | ||||
| 
 | ||||
| 
 | ||||
| def prepare_app_files(target, source, env): | ||||
|     files_section_node = next( | ||||
|         filter(lambda t: t.name.endswith(_FAP_FILEASSETS_SECTION), target) | ||||
|     ) | ||||
| 
 | ||||
|     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(): | ||||
|         raise UserError(f"File asset directory {directory} does not exist") | ||||
| 
 | ||||
|     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): | ||||
| @ -367,15 +374,15 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): | ||||
|     objcopy_str = ( | ||||
|         "${OBJCOPY} " | ||||
|         "--remove-section .ARM.attributes " | ||||
|         "--add-section .fapmeta=${SOURCE}.meta " | ||||
|         "--add-section ${_FAP_META_SECTION}=${SOURCE}${_FAP_META_SECTION} " | ||||
|     ) | ||||
| 
 | ||||
|     if app.fap_file_assets: | ||||
|         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 += ( | ||||
|         "--set-section-flags .fapmeta=contents,noload,readonly,data " | ||||
|         "--set-section-flags ${_FAP_META_SECTION}=contents,noload,readonly,data " | ||||
|         "--strip-debug --strip-unneeded " | ||||
|         "--add-gnu-debuglink=${SOURCE} " | ||||
|         "${SOURCES} ${TARGET}" | ||||
| @ -391,6 +398,51 @@ def generate_embed_app_metadata_actions(source, target, env, for_signature): | ||||
|     return Action(actions) | ||||
| 
 | ||||
| 
 | ||||
| def AddAppLaunchTarget(env, appname, launch_target_name): | ||||
|     deploy_sources, flipp_dist_paths, validators = [], [], [] | ||||
|     run_script_extra_ars = "" | ||||
| 
 | ||||
|     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): | ||||
|     env.SetDefault( | ||||
|         EXT_APPS_WORK_DIR="${FBT_FAP_DEBUG_ELF_ROOT}", | ||||
| @ -410,10 +462,14 @@ def generate(env, **kw): | ||||
|         EXT_APPS={},  # appid -> FlipperExternalAppInfo | ||||
|         EXT_LIBS={}, | ||||
|         _APP_ICONS=[], | ||||
|         _FAP_META_SECTION=_FAP_META_SECTION, | ||||
|         _FAP_FILEASSETS_SECTION=_FAP_FILEASSETS_SECTION, | ||||
|     ) | ||||
| 
 | ||||
|     env.AddMethod(BuildAppElf) | ||||
|     env.AddMethod(GetExtAppByIdOrPath) | ||||
|     env.AddMethod(AddAppLaunchTarget) | ||||
| 
 | ||||
|     env.Append( | ||||
|         BUILDERS={ | ||||
|             "FapDist": Builder( | ||||
|  | ||||
| @ -38,13 +38,13 @@ def ProcessSdkDepends(env, filename): | ||||
|     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], ".i.c")) | ||||
|     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]) | ||||
|     with open(mega_file, "wt") as sdk_c: | ||||
|         sdk_c.write( | ||||
| @ -87,6 +87,7 @@ class SdkMeta: | ||||
| class SdkTreeBuilder: | ||||
|     SDK_DIR_SUBST = "SDK_ROOT_DIR" | ||||
|     SDK_APP_EP_SUBST = "SDK_APP_EP_SUBST" | ||||
|     HEADER_EXTENSIONS = [".h", ".hpp"] | ||||
| 
 | ||||
|     def __init__(self, env, target, source) -> None: | ||||
|         self.env = env | ||||
| @ -111,7 +112,10 @@ class SdkTreeBuilder: | ||||
|             lines = LogicalLines(deps_f).readlines() | ||||
|             _, depends = lines[0].split(":", 1) | ||||
|             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("${SDK_DEFINITION}")) | ||||
| @ -180,12 +184,12 @@ class SdkTreeBuilder: | ||||
|         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) | ||||
|     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) | ||||
|     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]}") | ||||
|     current_sdk = SdkCollector() | ||||
|     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) | ||||
| 
 | ||||
| 
 | ||||
| def generate_sdk_symbols(source, target, env): | ||||
| def generate_api_table(source, target, env): | ||||
|     sdk_cache = SdkCache(source[0].path) | ||||
|     _check_sdk_is_up2date(sdk_cache) | ||||
| 
 | ||||
| @ -249,11 +253,11 @@ def generate_sdk_symbols(source, target, env): | ||||
| def generate(env, **kw): | ||||
|     if not env["VERBOSE"]: | ||||
|         env.SetDefault( | ||||
|             SDK_PREGEN_COMSTR="\tPREGEN\t${TARGET}", | ||||
|             SDK_COMSTR="\tSDKSRC\t${TARGET}", | ||||
|             SDK_AMALGAMATE_HEADER_COMSTR="\tAPIPREP\t${TARGET}", | ||||
|             SDK_AMALGAMATE_PP_COMSTR="\tAPIPP\t${TARGET}", | ||||
|             SDKSYM_UPDATER_COMSTR="\tSDKCHK\t${TARGET}", | ||||
|             SDKSYM_GENERATOR_COMSTR="\tSDKSYM\t${TARGET}", | ||||
|             SDKDEPLOY_COMSTR="\tSDKTREE\t${TARGET}", | ||||
|             APITABLE_GENERATOR_COMSTR="\tAPITBL\t${TARGET}", | ||||
|             SDKTREE_COMSTR="\tSDKTREE\t${TARGET}", | ||||
|         ) | ||||
| 
 | ||||
|     # Filtering out things cxxheaderparser cannot handle | ||||
| @ -274,40 +278,40 @@ def generate(env, **kw): | ||||
|     env.AddMethod(ProcessSdkDepends) | ||||
|     env.Append( | ||||
|         BUILDERS={ | ||||
|             "SDKPrebuilder": Builder( | ||||
|                 emitter=prebuild_sdk_emitter, | ||||
|             "ApiAmalgamator": Builder( | ||||
|                 emitter=api_amalgam_emitter, | ||||
|                 action=[ | ||||
|                     Action( | ||||
|                         prebuild_sdk_create_origin_file, | ||||
|                         "$SDK_PREGEN_COMSTR", | ||||
|                         api_amalgam_gen_origin_header, | ||||
|                         "$SDK_AMALGAMATE_HEADER_COMSTR", | ||||
|                     ), | ||||
|                     Action( | ||||
|                         "$CC -o $TARGET -E -P $CCFLAGS $_CCCOMCOM $SDK_PP_FLAGS -MMD ${TARGET}.c", | ||||
|                         "$SDK_COMSTR", | ||||
|                         "$SDK_AMALGAMATE_PP_COMSTR", | ||||
|                     ), | ||||
|                 ], | ||||
|                 suffix=".i", | ||||
|             ), | ||||
|             "SDKTree": Builder( | ||||
|             "SDKHeaderTreeExtractor": Builder( | ||||
|                 action=Action( | ||||
|                     deploy_sdk_tree_action, | ||||
|                     "$SDKDEPLOY_COMSTR", | ||||
|                     deploy_sdk_header_tree_action, | ||||
|                     "$SDKTREE_COMSTR", | ||||
|                 ), | ||||
|                 emitter=deploy_sdk_tree_emitter, | ||||
|                 emitter=deploy_sdk_header_tree_emitter, | ||||
|                 src_suffix=".d", | ||||
|             ), | ||||
|             "SDKSymUpdater": Builder( | ||||
|             "ApiTableValidator": Builder( | ||||
|                 action=Action( | ||||
|                     validate_sdk_cache, | ||||
|                     validate_api_cache, | ||||
|                     "$SDKSYM_UPDATER_COMSTR", | ||||
|                 ), | ||||
|                 suffix=".csv", | ||||
|                 src_suffix=".i", | ||||
|             ), | ||||
|             "SDKSymGenerator": Builder( | ||||
|             "ApiSymbolTable": Builder( | ||||
|                 action=Action( | ||||
|                     generate_sdk_symbols, | ||||
|                     "$SDKSYM_GENERATOR_COMSTR", | ||||
|                     generate_api_table, | ||||
|                     "$APITABLE_GENERATOR_COMSTR", | ||||
|                 ), | ||||
|                 suffix=".h", | ||||
|                 src_suffix=".csv", | ||||
|  | ||||
| @ -1,4 +1,6 @@ | ||||
| import SCons.Warnings as Warnings | ||||
| from SCons.Errors import UserError | ||||
| 
 | ||||
| 
 | ||||
| # from SCons.Script.Main import find_deepest_user_frame | ||||
| 
 | ||||
| @ -36,6 +38,11 @@ def fbt_warning(e): | ||||
| 
 | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -56,11 +56,11 @@ class StorageErrorCode(enum.Enum): | ||||
| 
 | ||||
| 
 | ||||
| class FlipperStorageException(Exception): | ||||
|     def __init__(self, message): | ||||
|         super().__init__(f"Storage error: {message}") | ||||
| 
 | ||||
|     def __init__(self, path: str, error_code: StorageErrorCode): | ||||
|         super().__init__(f"Storage error: path '{path}': {error_code.value}") | ||||
|     @staticmethod | ||||
|     def from_error_code(path: str, error_code: StorageErrorCode): | ||||
|         return FlipperStorageException( | ||||
|             f"Storage error: path '{path}': {error_code.value}" | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| class BufferedRead: | ||||
| @ -247,7 +247,9 @@ class FlipperStorage: | ||||
|                 if self.has_error(answer): | ||||
|                     last_error = self.get_error(answer) | ||||
|                     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.read.until(self.CLI_PROMPT) | ||||
| @ -319,7 +321,7 @@ class FlipperStorage: | ||||
|                 StorageErrorCode.INVALID_NAME, | ||||
|             ): | ||||
|                 return False | ||||
|             raise FlipperStorageException(path, error_code) | ||||
|             raise FlipperStorageException.from_error_code(path, error_code) | ||||
| 
 | ||||
|         return True | ||||
| 
 | ||||
| @ -333,7 +335,7 @@ class FlipperStorage: | ||||
| 
 | ||||
|     def _check_no_error(self, response, path=None): | ||||
|         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): | ||||
|         """file size on Flipper""" | ||||
|  | ||||
| @ -31,9 +31,10 @@ def parse_args(): | ||||
| 
 | ||||
| def get_commit_json(event): | ||||
|     context = ssl._create_unverified_context() | ||||
|     with urllib.request.urlopen( | ||||
|         event["pull_request"]["_links"]["commits"]["href"], context=context | ||||
|     ) as commit_file: | ||||
|     commit_url = event["pull_request"]["base"]["repo"]["commits_url"].replace( | ||||
|         "{/sha}", f"/{event['after']}" | ||||
|     ) | ||||
|     with urllib.request.urlopen(commit_url, context=context) as commit_file: | ||||
|         commit_json = json.loads(commit_file.read().decode("utf-8")) | ||||
|     return commit_json | ||||
| 
 | ||||
| @ -43,8 +44,8 @@ def get_details(event, args): | ||||
|     current_time = datetime.datetime.utcnow().date() | ||||
|     if args.type == "pull": | ||||
|         commit_json = get_commit_json(event) | ||||
|         data["commit_comment"] = shlex.quote(commit_json[-1]["commit"]["message"]) | ||||
|         data["commit_hash"] = commit_json[-1]["sha"] | ||||
|         data["commit_comment"] = shlex.quote(commit_json["commit"]["message"]) | ||||
|         data["commit_hash"] = commit_json["sha"] | ||||
|         ref = event["pull_request"]["head"]["ref"] | ||||
|         data["pull_id"] = event["pull_request"]["number"] | ||||
|         data["pull_name"] = shlex.quote(event["pull_request"]["title"]) | ||||
|  | ||||
| @ -1,13 +1,15 @@ | ||||
| #!/usr/bin/env python3 | ||||
| 
 | ||||
| from flipper.app import App | ||||
| from os.path import join, exists, relpath | ||||
| from os import makedirs, walk | ||||
| from update import Main as UpdateMain | ||||
| import json | ||||
| import shutil | ||||
| import zipfile | ||||
| import tarfile | ||||
| import zipfile | ||||
| from os import makedirs, walk | ||||
| from os.path import exists, join, relpath, basename, split | ||||
| 
 | ||||
| from ansi.color import fg | ||||
| from flipper.app import App | ||||
| from update import Main as UpdateMain | ||||
| 
 | ||||
| 
 | ||||
| class ProjectDir: | ||||
| @ -54,12 +56,19 @@ class Main(App): | ||||
|         if project_name == "firmware" and filetype != "elf": | ||||
|             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: | ||||
|         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) | ||||
| 
 | ||||
|     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}")): | ||||
|                 shutil.copyfile( | ||||
|                     src_file, | ||||
|                     self.get_dist_file_path( | ||||
|                         self.get_project_file_name(project, filetype) | ||||
|                     ), | ||||
|                     self.get_dist_path(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)): | ||||
|                 self.package_zip(foldertype, sdk_folder) | ||||
|                 self.note_dist_component(foldertype, "dir", sdk_folder) | ||||
| 
 | ||||
|     def package_zip(self, foldertype, sdk_folder): | ||||
|         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", | ||||
|             zipfile.ZIP_DEFLATED, | ||||
|         ) as zf: | ||||
| @ -94,7 +101,8 @@ class Main(App): | ||||
|                     ) | ||||
| 
 | ||||
|     def copy(self) -> int: | ||||
|         self.projects = dict( | ||||
|         self._dist_components: dict[str, str] = dict() | ||||
|         self.projects: dict[str, ProjectDir] = dict( | ||||
|             map( | ||||
|                 lambda pd: (pd.project, pd), | ||||
|                 map(ProjectDir, self.args.project), | ||||
| @ -122,12 +130,18 @@ class Main(App): | ||||
|             try: | ||||
|                 shutil.rmtree(self.output_dir_path) | ||||
|             except Exception as ex: | ||||
|                 pass | ||||
|                 self.logger.warn(f"Failed to clean output directory: {ex}") | ||||
| 
 | ||||
|         if not exists(self.output_dir_path): | ||||
|             self.logger.debug(f"Creating output directory {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(): | ||||
|             self.logger.debug(f"Copying {project.project} for {project.target}") | ||||
|             self.copy_single_project(project) | ||||
| 
 | ||||
|         self.logger.info( | ||||
| @ -137,10 +151,83 @@ class Main(App): | ||||
|         ) | ||||
| 
 | ||||
|         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}"[ | ||||
|             : 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 = [ | ||||
|             "generate", | ||||
|             "-d", | ||||
| @ -150,11 +237,11 @@ class Main(App): | ||||
|             "-t", | ||||
|             self.target, | ||||
|             "--dfu", | ||||
|                 self.get_dist_file_path( | ||||
|             self.get_dist_path( | ||||
|                 self.get_project_file_name(self.projects["firmware"], "dfu") | ||||
|             ), | ||||
|             "--stage", | ||||
|                 self.get_dist_file_path( | ||||
|             self.get_dist_path( | ||||
|                 self.get_project_file_name(self.projects["updater"], "bin") | ||||
|             ), | ||||
|         ] | ||||
| @ -168,6 +255,7 @@ class Main(App): | ||||
|         bundle_args.extend(self.other_args) | ||||
| 
 | ||||
|         if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0: | ||||
|             self.note_dist_component("update", "dir", bundle_dir) | ||||
|             self.logger.info( | ||||
|                 fg.boldgreen( | ||||
|                     f"Use this directory to self-update your Flipper:\n\t{bundle_dir}" | ||||
| @ -178,18 +266,18 @@ class Main(App): | ||||
|             with tarfile.open( | ||||
|                 join( | ||||
|                     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", | ||||
|                 compresslevel=9, | ||||
|                 format=tarfile.USTAR_FORMAT, | ||||
|             ) as tar: | ||||
|                 self.note_dist_component( | ||||
|                     "update", "tgz", self.get_dist_path(bundle_tgz) | ||||
|                 ) | ||||
|                 tar.add(bundle_dir, arcname=bundle_dir_name) | ||||
| 
 | ||||
|         return bundle_result | ||||
| 
 | ||||
|         return 0 | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     Main()() | ||||
|  | ||||
| @ -8,7 +8,7 @@ import sys | ||||
| def main(): | ||||
|     logger = logging.getLogger() | ||||
|     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 | ||||
|     subprocess.call( | ||||
|         [ | ||||
|  | ||||
| @ -15,10 +15,12 @@ if not ["%FBT_NOENV%"] == [""] ( | ||||
| 
 | ||||
| set "FLIPPER_TOOLCHAIN_VERSION=21" | ||||
| 
 | ||||
| if ["%FBT_TOOLCHAIN_ROOT%"] == [""] ( | ||||
|     set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows" | ||||
| if ["%FBT_TOOLCHAIN_PATH%"] == [""] ( | ||||
|     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" | ||||
| 
 | ||||
| if not exist "%FBT_TOOLCHAIN_ROOT%" ( | ||||
|  | ||||
| @ -4,9 +4,15 @@ | ||||
| 
 | ||||
| # public variables | ||||
| DEFAULT_SCRIPT_PATH="$(pwd -P)"; | ||||
| SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}"; | ||||
| 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:-""}"; | ||||
| 
 | ||||
| fbtenv_show_usage() | ||||
| @ -60,7 +66,6 @@ fbtenv_restore_env() | ||||
|     unset SAVED_PYTHONPATH; | ||||
|     unset SAVED_PYTHONHOME; | ||||
| 
 | ||||
|     unset SCRIPT_PATH; | ||||
|     unset FBT_TOOLCHAIN_VERSION; | ||||
|     unset FBT_TOOLCHAIN_PATH; | ||||
| } | ||||
| @ -104,13 +109,14 @@ fbtenv_set_shell_prompt() | ||||
|     return 0;  # all other shells | ||||
| } | ||||
| 
 | ||||
| fbtenv_check_script_path() | ||||
| fbtenv_check_env_vars() | ||||
| { | ||||
|     if [ ! -x "$SCRIPT_PATH/fbt" ] && [ ! -x "$SCRIPT_PATH/ufbt" ] ; then | ||||
|         echo "Please source this script from [u]fbt root directory, or specify 'SCRIPT_PATH' variable manually"; | ||||
|     # Return error if FBT_TOOLCHAIN_PATH is not set before script is sourced or if fbt executable is not in DEFAULT_SCRIPT_PATH | ||||
|     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:"; | ||||
|         printf "\tSCRIPT_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" | ||||
|         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 FBT_TOOLCHAIN_PATH' and try again" | ||||
|         return 1; | ||||
|     fi | ||||
|     return 0; | ||||
| @ -207,7 +213,7 @@ fbtenv_show_unpack_percentage() | ||||
| 
 | ||||
| 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; | ||||
|     mkdir -p "$FBT_TOOLCHAIN_PATH/toolchain" || return 1; | ||||
|     mv "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_DIR" "$TOOLCHAIN_ARCH_DIR" || return 1; | ||||
| @ -215,7 +221,7 @@ fbtenv_unpack_toolchain() | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| fbtenv_clearing() | ||||
| fbtenv_cleanup() | ||||
| { | ||||
|     printf "Cleaning up.."; | ||||
|     if [ -n "${FBT_TOOLCHAIN_PATH:-""}" ]; then | ||||
| @ -270,14 +276,14 @@ fbtenv_download_toolchain() | ||||
|     fbtenv_check_tar || return 1; | ||||
|     TOOLCHAIN_TAR="$(basename "$TOOLCHAIN_URL")"; | ||||
|     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 | ||||
|         fbtenv_curl_wget_check || return 1; | ||||
|         fbtenv_download_toolchain_tar || return 1; | ||||
|     fi | ||||
|     fbtenv_remove_old_tooclhain; | ||||
|     fbtenv_unpack_toolchain || return 1; | ||||
|     fbtenv_clearing; | ||||
|     fbtenv_cleanup; | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| @ -296,8 +302,8 @@ fbtenv_main() | ||||
|         fbtenv_restore_env; | ||||
|         return 0; | ||||
|     fi | ||||
|     fbtenv_check_if_sourced_multiple_times;  # many source it's just a warning | ||||
|     fbtenv_check_script_path || return 1; | ||||
|     fbtenv_check_if_sourced_multiple_times; | ||||
|     fbtenv_check_env_vars || return 1; | ||||
|     fbtenv_check_download_toolchain || return 1; | ||||
|     fbtenv_set_shell_prompt; | ||||
|     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"], []) | ||||
| 
 | ||||
| 
 | ||||
| if appsrc := appenv.subst("$APPSRC"): | ||||
|     deploy_sources, flipp_dist_paths, validators = [], [], [] | ||||
|     run_script_extra_ars = "" | ||||
|     appenv.AddAppLaunchTarget(appsrc, "launch_app") | ||||
| 
 | ||||
|     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_origin_path = "${BUILD_DIR}/sdk_origin" | ||||
| sdk_source = appenv.SDKPrebuilder( | ||||
|     sdk_origin_path, | ||||
| amalgamated_api = "${BUILD_DIR}/sdk_origin" | ||||
| sdk_source = appenv.ApiAmalgamator( | ||||
|     amalgamated_api, | ||||
|     # Deps on root SDK headers and generated files | ||||
|     (appenv["SDK_HEADERS"], appenv["FW_ASSETS_HEADERS"]), | ||||
| ) | ||||
| # Extra deps on headers included in deeper levels | ||||
| # 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") | ||||
| sdk_tree = appenv.SDKTree(appenv["SDK_DIR"], sdk_origin_path) | ||||
| appenv["SDK_DIR"] = appenv.Dir("${BUILD_DIR}/sdk_headers") | ||||
| sdk_header_tree = appenv.SDKHeaderTreeExtractor(appenv["SDK_DIR"], amalgamated_api) | ||||
| # AlwaysBuild(sdk_tree) | ||||
| Alias("sdk_tree", sdk_tree) | ||||
| extapps.sdk_tree = sdk_tree | ||||
| Alias("sdk_tree", sdk_header_tree) | ||||
| extapps.sdk_tree = sdk_header_tree | ||||
| 
 | ||||
| sdk_apicheck = appenv.SDKSymUpdater(appenv["SDK_DEFINITION"], sdk_origin_path) | ||||
| Precious(sdk_apicheck) | ||||
| NoClean(sdk_apicheck) | ||||
| AlwaysBuild(sdk_apicheck) | ||||
| Alias("sdk_check", sdk_apicheck) | ||||
| api_check = appenv.ApiTableValidator(appenv["SDK_DEFINITION"], amalgamated_api) | ||||
| Precious(api_check) | ||||
| NoClean(api_check) | ||||
| AlwaysBuild(api_check) | ||||
| Alias("api_check", api_check) | ||||
| 
 | ||||
| sdk_apisyms = appenv.SDKSymGenerator( | ||||
|     "${BUILD_DIR}/assets/compiled/symbols.h", appenv["SDK_DEFINITION"] | ||||
| firmware_apitable = appenv.ApiSymbolTable( | ||||
|     "${BUILD_DIR}/assets/compiled/firmware_api_table.h", appenv["SDK_DEFINITION"] | ||||
| ) | ||||
| Alias("api_syms", sdk_apisyms) | ||||
| Alias("api_table", firmware_apitable) | ||||
| ENV.Replace( | ||||
|     SDK_APISYMS=sdk_apisyms, | ||||
|     FW_API_TABLE=firmware_apitable, | ||||
|     _APP_ICONS=appenv["_APP_ICONS"], | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| 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") | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 hedger
						hedger