Merge branch 'fz-dev' into dev

This commit is contained in:
MX 2023-04-06 07:14:48 +03:00
commit aac7654ca3
No known key found for this signature in database
GPG key ID: 7CCC66B7DBDD1C83
55 changed files with 1771 additions and 304 deletions

View file

@ -51,10 +51,10 @@ static void dap_main_view_draw_callback(Canvas* canvas, void* _model) {
canvas_set_color(canvas, ColorBlack);
if(model->dap_active) {
canvas_draw_icon(canvas, 14, 16, &I_ArrowUpFilled_12x18);
canvas_draw_icon(canvas, 28, 16, &I_ArrowDownFilled_12x18);
canvas_draw_icon_ex(canvas, 28, 16, &I_ArrowUpFilled_12x18, IconRotation180);
} else {
canvas_draw_icon(canvas, 14, 16, &I_ArrowUpEmpty_12x18);
canvas_draw_icon(canvas, 28, 16, &I_ArrowDownEmpty_12x18);
canvas_draw_icon_ex(canvas, 28, 16, &I_ArrowUpEmpty_12x18, IconRotation180);
}
switch(model->mode) {
@ -76,9 +76,9 @@ static void dap_main_view_draw_callback(Canvas* canvas, void* _model) {
}
if(model->rx_active) {
canvas_draw_icon(canvas, 101, 16, &I_ArrowDownFilled_12x18);
canvas_draw_icon_ex(canvas, 101, 16, &I_ArrowUpFilled_12x18, IconRotation180);
} else {
canvas_draw_icon(canvas, 101, 16, &I_ArrowDownEmpty_12x18);
canvas_draw_icon_ex(canvas, 101, 16, &I_ArrowUpEmpty_12x18, IconRotation180);
}
canvas_draw_str_aligned(canvas, 100, 38, AlignCenter, AlignTop, "UART");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 B

View file

@ -80,9 +80,9 @@ static void gpio_usb_uart_draw_callback(Canvas* canvas, void* _model) {
canvas_draw_icon(canvas, 48, 14, &I_ArrowUpEmpty_14x15);
if(model->rx_active)
canvas_draw_icon(canvas, 48, 34, &I_ArrowDownFilled_14x15);
canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpFilled_14x15, IconRotation180);
else
canvas_draw_icon(canvas, 48, 34, &I_ArrowDownEmpty_14x15);
canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpEmpty_14x15, IconRotation180);
}
static bool gpio_usb_uart_input_callback(InputEvent* event, void* context) {

View file

@ -37,7 +37,7 @@ static void desktop_loader_callback(const void* message, void* context) {
static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) {
UNUSED(context);
furi_assert(canvas);
canvas_draw_icon(canvas, 0, 0, &I_Lock_8x8);
canvas_draw_icon(canvas, 0, 0, &I_Lock_7x8);
}
static void desktop_dummy_mode_icon_draw_callback(Canvas* canvas, void* context) {
@ -233,7 +233,7 @@ Desktop* desktop_alloc() {
// Lock icon
desktop->lock_icon_viewport = view_port_alloc();
view_port_set_width(desktop->lock_icon_viewport, icon_get_width(&I_Lock_8x8));
view_port_set_width(desktop->lock_icon_viewport, icon_get_width(&I_Lock_7x8));
view_port_draw_callback_set(
desktop->lock_icon_viewport, desktop_lock_icon_draw_callback, desktop);
view_port_enabled_set(desktop->lock_icon_viewport, false);

View file

@ -115,16 +115,18 @@ static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInpu
} else {
switch(model->pin.data[i]) {
case InputKeyDown:
canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_down_7x9);
canvas_draw_icon_ex(
canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9, IconRotation180);
break;
case InputKeyUp:
canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9);
canvas_draw_icon_ex(canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9, IconRotation0);
break;
case InputKeyLeft:
canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_left_9x7);
canvas_draw_icon_ex(
canvas, x + 2, y + 3, &I_Pin_arrow_up_7x9, IconRotation270);
break;
case InputKeyRight:
canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_right_9x7);
canvas_draw_icon_ex(canvas, x + 2, y + 3, &I_Pin_arrow_up_7x9, IconRotation90);
break;
default:
furi_assert(0);
@ -147,7 +149,8 @@ static void desktop_view_pin_input_draw(Canvas* canvas, void* context) {
desktop_view_pin_input_draw_cells(canvas, model);
if((model->pin.length > 0) && !model->locked_input) {
canvas_draw_icon(canvas, 4, 53, &I_Pin_back_full_40x8);
canvas_draw_icon(canvas, 4, 53, &I_Pin_back_arrow_10x8);
canvas_draw_str(canvas, 16, 60, "= clear");
}
if(model->button_label && ((model->pin.length >= MIN_PIN_SIZE) || model->locked_input)) {

View file

@ -224,7 +224,7 @@ void canvas_draw_bitmap(
y += canvas->offset_y;
uint8_t* bitmap_data = NULL;
compress_icon_decode(canvas->compress_icon, compressed_bitmap_data, &bitmap_data);
u8g2_DrawXBM(&canvas->fb, x, y, width, height, bitmap_data);
canvas_draw_u8g2_bitmap(&canvas->fb, x, y, width, height, bitmap_data, IconRotation0);
}
void canvas_draw_icon_animation(
@ -240,13 +240,138 @@ void canvas_draw_icon_animation(
uint8_t* icon_data = NULL;
compress_icon_decode(
canvas->compress_icon, icon_animation_get_data(icon_animation), &icon_data);
u8g2_DrawXBM(
canvas_draw_u8g2_bitmap(
&canvas->fb,
x,
y,
icon_animation_get_width(icon_animation),
icon_animation_get_height(icon_animation),
icon_data);
icon_data,
IconRotation0);
}
static void canvas_draw_u8g2_bitmap_int(
u8g2_t* u8g2,
u8g2_uint_t x,
u8g2_uint_t y,
u8g2_uint_t w,
u8g2_uint_t h,
bool mirror,
bool rotation,
const uint8_t* bitmap) {
u8g2_uint_t blen;
blen = w;
blen += 7;
blen >>= 3;
if(rotation && !mirror) {
x += w + 1;
} else if(mirror && !rotation) {
y += h - 1;
}
while(h > 0) {
const uint8_t* b = bitmap;
uint16_t len = w;
uint16_t x0 = x;
uint16_t y0 = y;
uint8_t mask;
uint8_t color = u8g2->draw_color;
uint8_t ncolor = (color == 0 ? 1 : 0);
mask = 1;
while(len > 0) {
if(u8x8_pgm_read(b) & mask) {
u8g2->draw_color = color;
u8g2_DrawHVLine(u8g2, x0, y0, 1, 0);
} else if(u8g2->bitmap_transparency == 0) {
u8g2->draw_color = ncolor;
u8g2_DrawHVLine(u8g2, x0, y0, 1, 0);
}
if(rotation) {
y0++;
} else {
x0++;
}
mask <<= 1;
if(mask == 0) {
mask = 1;
b++;
}
len--;
}
u8g2->draw_color = color;
bitmap += blen;
if(mirror) {
if(rotation) {
x++;
} else {
y--;
}
} else {
if(rotation) {
x--;
} else {
y++;
}
}
h--;
}
}
void canvas_draw_u8g2_bitmap(
u8g2_t* u8g2,
u8g2_uint_t x,
u8g2_uint_t y,
u8g2_uint_t w,
u8g2_uint_t h,
const uint8_t* bitmap,
IconRotation rotation) {
u8g2_uint_t blen;
blen = w;
blen += 7;
blen >>= 3;
#ifdef U8G2_WITH_INTERSECTION
if(u8g2_IsIntersection(u8g2, x, y, x + w, y + h) == 0) return;
#endif /* U8G2_WITH_INTERSECTION */
switch(rotation) {
case IconRotation0:
canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 0, 0, bitmap);
break;
case IconRotation90:
canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 0, 1, bitmap);
break;
case IconRotation180:
canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 1, 0, bitmap);
break;
case IconRotation270:
canvas_draw_u8g2_bitmap_int(u8g2, x, y, w, h, 1, 1, bitmap);
break;
default:
break;
}
}
void canvas_draw_icon_ex(
Canvas* canvas,
uint8_t x,
uint8_t y,
const Icon* icon,
IconRotation rotation) {
furi_assert(canvas);
furi_assert(icon);
x += canvas->offset_x;
y += canvas->offset_y;
uint8_t* icon_data = NULL;
compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data);
canvas_draw_u8g2_bitmap(
&canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, rotation);
}
void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) {
@ -257,7 +382,8 @@ void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) {
y += canvas->offset_y;
uint8_t* icon_data = NULL;
compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data);
u8g2_DrawXBM(&canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data);
canvas_draw_u8g2_bitmap(
&canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data, IconRotation0);
}
void canvas_draw_dot(Canvas* canvas, uint8_t x, uint8_t y) {
@ -367,7 +493,7 @@ void canvas_draw_xbm(
furi_assert(canvas);
x += canvas->offset_x;
y += canvas->offset_y;
u8g2_DrawXBM(&canvas->fb, x, y, w, h, bitmap);
canvas_draw_u8g2_bitmap(&canvas->fb, x, y, w, h, bitmap, IconRotation0);
}
void canvas_draw_glyph(Canvas* canvas, uint8_t x, uint8_t y, uint16_t ch) {

View file

@ -65,6 +65,22 @@ typedef struct {
uint8_t descender;
} CanvasFontParameters;
/** Icon flip */
typedef enum {
IconFlipNone,
IconFlipHorizontal,
IconFlipVertical,
IconFlipBoth,
} IconFlip;
/** Icon rotation */
typedef enum {
IconRotation0,
IconRotation90,
IconRotation180,
IconRotation270,
} IconRotation;
/** Canvas anonymous structure */
typedef struct Canvas Canvas;
@ -218,6 +234,22 @@ void canvas_draw_bitmap(
uint8_t height,
const uint8_t* compressed_bitmap_data);
/** Draw icon at position defined by x,y with rotation and flip.
*
* @param canvas Canvas instance
* @param x x coordinate
* @param y y coordinate
* @param icon Icon instance
* @param flip IconFlip
* @param rotation IconRotation
*/
void canvas_draw_icon_ex(
Canvas* canvas,
uint8_t x,
uint8_t y,
const Icon* icon,
IconRotation rotation);
/** Draw animation at position defined by x,y.
*
* @param canvas Canvas instance

View file

@ -83,6 +83,25 @@ void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation);
*/
CanvasOrientation canvas_get_orientation(const Canvas* canvas);
/** Draw a u8g2 bitmap
*
* @param canvas Canvas instance
* @param x x coordinate
* @param y y coordinate
* @param width width
* @param height height
* @param bitmap bitmap
* @param rotation rotation
*/
void canvas_draw_u8g2_bitmap(
u8g2_t* u8g2,
uint8_t x,
uint8_t y,
uint8_t width,
uint8_t height,
const uint8_t* bitmap,
uint8_t rotation);
#ifdef __cplusplus
}
#endif

View file

@ -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!");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 B

View file

@ -75,12 +75,12 @@ Example for building an app from Rust sources:
Library sources must be placed in a subfolder of the `lib` folder within the application's source folder.
Each library is defined as a call to the `Lib()` function, accepting the following parameters:
- **name**: name of the library's folder. Required.
- **fap_include_paths**: list of the library's relative paths to add to the parent fap's include path list. The default value is `["."]`, meaning the library's source root.
- **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`.
- **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`.
- **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`.
- **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`.
- **name**: name of the library's folder. Required.
- **fap_include_paths**: list of the library's relative paths to add to the parent fap's include path list. The default value is `["."]`, meaning the library's source root.
- **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`.
- **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`.
- **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`.
- **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`.
Example for building an app with a private library:

View file

@ -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.

View file

@ -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"],
],
)

View file

@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,20.0,,
Version,+,20.1,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_vcp.h,,
@ -540,6 +540,7 @@ Function,+,canvas_draw_frame,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t"
Function,+,canvas_draw_glyph,void,"Canvas*, uint8_t, uint8_t, uint16_t"
Function,+,canvas_draw_icon,void,"Canvas*, uint8_t, uint8_t, const Icon*"
Function,+,canvas_draw_icon_animation,void,"Canvas*, uint8_t, uint8_t, IconAnimation*"
Function,+,canvas_draw_icon_ex,void,"Canvas*, uint8_t, uint8_t, const Icon*, IconRotation"
Function,+,canvas_draw_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t"
Function,+,canvas_draw_rbox,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t"
Function,+,canvas_draw_rframe,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t"
@ -876,12 +877,6 @@ Function,-,furi_hal_clock_resume_tick,void,
Function,-,furi_hal_clock_suspend_tick,void,
Function,-,furi_hal_clock_switch_to_hsi,void,
Function,-,furi_hal_clock_switch_to_pll,void,
Function,-,compress_alloc,Compress*,uint16_t
Function,-,compress_decode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*"
Function,-,compress_encode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*"
Function,-,compress_free,void,Compress*
Function,-,compress_icon_decode,void,"const uint8_t*, uint8_t**"
Function,-,compress_icon_init,void,
Function,+,furi_hal_console_disable,void,
Function,+,furi_hal_console_enable,void,
Function,+,furi_hal_console_init,void,

1 entry status name type params
2 Version + 20.0 20.1
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/cli/cli.h
5 Header + applications/services/cli/cli_vcp.h
540 Function + canvas_draw_glyph void Canvas*, uint8_t, uint8_t, uint16_t
541 Function + canvas_draw_icon void Canvas*, uint8_t, uint8_t, const Icon*
542 Function + canvas_draw_icon_animation void Canvas*, uint8_t, uint8_t, IconAnimation*
543 Function + canvas_draw_icon_ex void Canvas*, uint8_t, uint8_t, const Icon*, IconRotation
544 Function + canvas_draw_line void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t
545 Function + canvas_draw_rbox void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t
546 Function + canvas_draw_rframe void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t
877 Function - furi_hal_clock_suspend_tick void
878 Function - furi_hal_clock_switch_to_hsi void
879 Function - furi_hal_clock_switch_to_pll void
Function - compress_alloc Compress* uint16_t
Function - compress_decode _Bool Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*
Function - compress_encode _Bool Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*
Function - compress_free void Compress*
Function - compress_icon_decode void const uint8_t*, uint8_t**
Function - compress_icon_init void
880 Function + furi_hal_console_disable void
881 Function + furi_hal_console_enable void
882 Function + furi_hal_console_init void

View file

@ -631,6 +631,7 @@ Function,+,canvas_draw_glyph,void,"Canvas*, uint8_t, uint8_t, uint16_t"
Function,+,canvas_draw_icon,void,"Canvas*, uint8_t, uint8_t, const Icon*"
Function,+,canvas_draw_icon_animation,void,"Canvas*, uint8_t, uint8_t, IconAnimation*"
Function,+,canvas_draw_icon_bitmap,void,"Canvas*, uint8_t, uint8_t, int16_t, int16_t, const Icon*"
Function,+,canvas_draw_icon_ex,void,"Canvas*, uint8_t, uint8_t, const Icon*, IconRotation"
Function,+,canvas_draw_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t"
Function,+,canvas_draw_rbox,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t"
Function,+,canvas_draw_rframe,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t"

1 entry status name type params
631 Function + canvas_draw_icon void Canvas*, uint8_t, uint8_t, const Icon*
632 Function + canvas_draw_icon_animation void Canvas*, uint8_t, uint8_t, IconAnimation*
633 Function + canvas_draw_icon_bitmap void Canvas*, uint8_t, uint8_t, int16_t, int16_t, const Icon*
634 Function + canvas_draw_icon_ex void Canvas*, uint8_t, uint8_t, const Icon*, IconRotation
635 Function + canvas_draw_line void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t
636 Function + canvas_draw_rbox void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t
637 Function + canvas_draw_rframe void Canvas*, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t

View file

@ -2,16 +2,14 @@
#include <furi_hal.h>
#include "sector_cache.h"
static volatile DSTATUS Stat = STA_NOINIT;
static DSTATUS driver_check_status(BYTE lun) {
UNUSED(lun);
Stat = STA_NOINIT;
if(sd_get_card_state() == SdSpiStatusOK) {
Stat &= ~STA_NOINIT;
DSTATUS status = 0;
if(sd_get_card_state() != SdSpiStatusOK) {
status = STA_NOINIT;
}
return Stat;
return status;
}
static DSTATUS driver_initialize(BYTE pdrv);
@ -107,6 +105,16 @@ static bool sd_device_write(uint32_t* buff, uint32_t sector, uint32_t count) {
* @retval DSTATUS: Operation status
*/
static DSTATUS driver_initialize(BYTE pdrv) {
UNUSED(pdrv);
return RES_OK;
}
/**
* @brief Gets Disk Status
* @param pdrv: Physical drive number (0..)
* @retval DSTATUS: Operation status
*/
static DSTATUS driver_status(BYTE pdrv) {
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast);
furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast;
@ -118,16 +126,6 @@ static DSTATUS driver_initialize(BYTE pdrv) {
return status;
}
/**
* @brief Gets Disk Status
* @param pdrv: Physical drive number (0..)
* @retval DSTATUS: Operation status
*/
static DSTATUS driver_status(BYTE pdrv) {
UNUSED(pdrv);
return Stat;
}
/**
* @brief Reads Sector(s)
* @param pdrv: Physical drive number (0..)
@ -224,15 +222,15 @@ static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT coun
* @retval DRESULT: Operation result
*/
static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff) {
UNUSED(pdrv);
DRESULT res = RES_ERROR;
SD_CardInfo CardInfo;
if(Stat & STA_NOINIT) return RES_NOTRDY;
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_fast);
furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_fast;
DSTATUS status = driver_check_status(pdrv);
if(status & STA_NOINIT) return RES_NOTRDY;
switch(cmd) {
/* Make sure that no pending write process */
case CTRL_SYNC:

View file

@ -53,8 +53,8 @@ void furi_hal_gpio_init(
const GpioPull pull,
const GpioSpeed speed) {
// we cannot set alternate mode in this function
furi_assert(mode != GpioModeAltFunctionPushPull);
furi_assert(mode != GpioModeAltFunctionOpenDrain);
furi_check(mode != GpioModeAltFunctionPushPull);
furi_check(mode != GpioModeAltFunctionOpenDrain);
furi_hal_gpio_init_ex(gpio, mode, pull, speed, GpioAltFnUnused);
}
@ -178,7 +178,7 @@ void furi_hal_gpio_add_int_callback(const GpioPin* gpio, GpioExtiCallback cb, vo
FURI_CRITICAL_ENTER();
uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio);
furi_assert(gpio_interrupt[pin_num].callback == NULL);
furi_check(gpio_interrupt[pin_num].callback == NULL);
gpio_interrupt[pin_num].callback = cb;
gpio_interrupt[pin_num].context = ctx;
gpio_interrupt[pin_num].ready = true;

View file

@ -2,29 +2,24 @@
#include <furi_hal.h>
#include <flipper.h>
#include <alt_boot.h>
#include <u8g2_glue.h>
#include <assets_icons.h>
#include <toolbox/compress.h>
#include <gui/canvas.h>
#include <gui/canvas_i.h>
void flipper_boot_dfu_show_splash() {
// Initialize
CompressIcon* compress_icon = compress_icon_alloc();
Canvas* canvas = canvas_init();
u8g2_t* fb = malloc(sizeof(u8g2_t));
memset(fb, 0, sizeof(u8g2_t));
u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32);
u8g2_InitDisplay(fb);
u8g2_SetDrawColor(fb, 0x01);
uint8_t* splash_data = NULL;
compress_icon_decode(compress_icon, icon_get_data(&I_DFU_128x50), &splash_data);
u8g2_DrawXBM(fb, 0, 64 - 50, 128, 50, splash_data);
u8g2_SetFont(fb, u8g2_font_helvB08_tr);
u8g2_DrawStr(fb, 2, 8, "Update & Recovery Mode");
u8g2_DrawStr(fb, 2, 21, "DFU Started");
u8g2_SetPowerSave(fb, 0);
u8g2_SendBuffer(fb);
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary);
compress_icon_free(compress_icon);
canvas_draw_icon(canvas, 0, 64 - 50, &I_DFU_128x50);
canvas_draw_str(canvas, 2, 8, "Update & Recovery Mode");
canvas_draw_str(canvas, 2, 21, "DFU Started");
canvas_commit(canvas);
canvas_free(canvas);
}
void flipper_boot_dfu_exec() {

View file

@ -2,44 +2,43 @@
#include <furi_hal.h>
#include <flipper.h>
#include <alt_boot.h>
#include <u8g2_glue.h>
#include <assets_icons.h>
#include <toolbox/compress.h>
#include <gui/canvas.h>
#include <gui/canvas_i.h>
#define COUNTER_VALUE (136U)
static void flipper_boot_recovery_draw_splash(u8g2_t* fb, size_t progress) {
static void flipper_boot_recovery_draw_progress(Canvas* canvas, size_t progress) {
if(progress < COUNTER_VALUE) {
// Fill the progress bar while the progress is going down
u8g2_SetDrawColor(fb, 0x01);
u8g2_DrawRFrame(fb, 59, 41, 69, 8, 2);
canvas_draw_rframe(canvas, 59, 41, 69, 8, 2);
size_t width = (COUNTER_VALUE - progress) * 68 / COUNTER_VALUE;
u8g2_DrawBox(fb, 60, 42, width, 6);
canvas_draw_box(canvas, 60, 42, width, 6);
} else {
u8g2_SetDrawColor(fb, 0x00);
u8g2_DrawRBox(fb, 59, 41, 69, 8, 2);
canvas_draw_rframe(canvas, 59, 41, 69, 8, 2);
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 60, 42, 67, 6);
canvas_set_color(canvas, ColorBlack);
}
u8g2_SendBuffer(fb);
canvas_commit(canvas);
}
void flipper_boot_recovery_draw_splash(Canvas* canvas) {
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary);
canvas_draw_icon(canvas, 0, 0, &I_Erase_pin_128x64);
canvas_commit(canvas);
}
void flipper_boot_recovery_exec() {
u8g2_t* fb = malloc(sizeof(u8g2_t));
u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32);
u8g2_InitDisplay(fb);
Canvas* canvas = canvas_init();
CompressIcon* compress_icon = compress_icon_alloc();
uint8_t* splash_data = NULL;
compress_icon_decode(compress_icon, icon_get_data(&I_Erase_pin_128x64), &splash_data);
u8g2_ClearBuffer(fb);
u8g2_SetDrawColor(fb, 0x01);
// Draw the recovery picture
u8g2_DrawXBM(fb, 0, 0, 128, 64, splash_data);
u8g2_SendBuffer(fb);
u8g2_SetPowerSave(fb, 0);
compress_icon_free(compress_icon);
// Show recovery splashscreen
flipper_boot_recovery_draw_splash(canvas);
size_t counter = COUNTER_VALUE;
while(counter) {
@ -53,7 +52,7 @@ void flipper_boot_recovery_exec() {
counter = COUNTER_VALUE;
}
flipper_boot_recovery_draw_splash(fb, counter);
flipper_boot_recovery_draw_progress(canvas, counter);
}
if(!counter) {

View file

@ -45,16 +45,19 @@ void platformDisableIrqCallback() {
void platformSetIrqCallback(PlatformIrqCallback callback) {
rfal_platform.callback = callback;
rfal_platform.thread =
furi_thread_alloc_ex("RfalIrqDriver", 1024, rfal_platform_irq_thread, NULL);
furi_thread_mark_as_service(rfal_platform.thread);
furi_thread_set_priority(rfal_platform.thread, FuriThreadPriorityIsr);
furi_thread_start(rfal_platform.thread);
furi_hal_gpio_add_int_callback(&gpio_nfc_irq_rfid_pull, nfc_isr, NULL);
// Disable interrupt callback as the pin is shared between 2 apps
// It is enabled in rfalLowPowerModeStop()
furi_hal_gpio_disable_int_callback(&gpio_nfc_irq_rfid_pull);
if(!rfal_platform.thread) {
rfal_platform.thread =
furi_thread_alloc_ex("RfalIrqDriver", 1024, rfal_platform_irq_thread, NULL);
furi_thread_mark_as_service(rfal_platform.thread);
furi_thread_set_priority(rfal_platform.thread, FuriThreadPriorityIsr);
furi_thread_start(rfal_platform.thread);
furi_hal_gpio_add_int_callback(&gpio_nfc_irq_rfid_pull, nfc_isr, NULL);
// Disable interrupt callback as the pin is shared between 2 apps
// It is enabled in rfalLowPowerModeStop()
furi_hal_gpio_disable_int_callback(&gpio_nfc_irq_rfid_pull);
}
}
bool platformSpiTxRx(const uint8_t* txBuf, uint8_t* rxBuf, uint16_t len) {

View file

@ -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 = (

View file

@ -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(

View file

@ -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",

View file

@ -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

View file

@ -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"""

View file

@ -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"])

View file

@ -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, environ
from update import Main as UpdateMain
import json
import shutil
import zipfile
import tarfile
import zipfile
from os import makedirs, walk, environ
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,67 +151,141 @@ class Main(App):
)
if self.args.version:
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_args = [
"generate",
"-d",
bundle_dir,
"-v",
self.args.version,
"-t",
self.target,
"--dfu",
self.get_dist_file_path(
self.get_project_file_name(self.projects["firmware"], "dfu")
),
"--stage",
self.get_dist_file_path(
self.get_project_file_name(self.projects["updater"], "bin")
),
]
if self.args.resources:
bundle_args.extend(
(
"-r",
self.args.resources,
)
)
bundle_args.extend(self.other_args)
log_custom_fz_name = (
environ.get("CUSTOM_FLIPPER_NAME", None)
or ""
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,
)
if (log_custom_fz_name != "") and (len(log_custom_fz_name) <= 8) and (log_custom_fz_name.isalnum()) and (log_custom_fz_name.isascii()):
self.logger.info(
f"Flipper Custom Name is set:\n\tName: {log_custom_fz_name} : length - {len(log_custom_fz_name)} chars"
)
if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0:
self.logger.info(
fg.boldgreen(
f"Use this directory to self-update your Flipper:\n\t{bundle_dir}"
)
)
# Create tgz archive
with tarfile.open(
join(
self.output_dir_path,
f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz",
),
"w:gz",
compresslevel=9,
format=tarfile.USTAR_FORMAT,
) as tar:
tar.add(bundle_dir, arcname=bundle_dir_name)
return bundle_result
):
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 = self.get_dist_path(bundle_dir_name)
bundle_args = [
"generate",
"-d",
bundle_dir,
"-v",
self.args.version,
"-t",
self.target,
"--dfu",
self.get_dist_path(
self.get_project_file_name(self.projects["firmware"], "dfu")
),
"--stage",
self.get_dist_path(
self.get_project_file_name(self.projects["updater"], "bin")
),
]
if self.args.resources:
bundle_args.extend(
(
"-r",
self.args.resources,
)
)
bundle_args.extend(self.other_args)
log_custom_fz_name = (
environ.get("CUSTOM_FLIPPER_NAME", None)
or ""
)
if (log_custom_fz_name != "") and (len(log_custom_fz_name) <= 8) and (log_custom_fz_name.isalnum()) and (log_custom_fz_name.isascii()):
self.logger.info(f"Flipper Custom Name is set:\n\tName: {log_custom_fz_name} : length - {len(log_custom_fz_name)} chars"
)
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}"
)
)
# Create tgz archive
with tarfile.open(
join(
self.output_dir_path,
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
if __name__ == "__main__":
Main()()

View file

@ -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(
[

View file

@ -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%" (

View file

@ -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;
@ -217,7 +223,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;
@ -225,7 +231,7 @@ fbtenv_unpack_toolchain()
return 0;
}
fbtenv_clearing()
fbtenv_cleanup()
{
printf "Cleaning up..";
if [ -n "${FBT_TOOLCHAIN_PATH:-""}" ]; then
@ -280,14 +286,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;
}
@ -306,8 +312,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
View 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)

View 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")

View 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
...

View 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

View file

@ -0,0 +1,4 @@
dist/*
.vscode
.clang-format
.editorconfig

View 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
}

View 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"
]
}

View 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",
},
]
}

View 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"
}
}

View 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"
}
]
}

View 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

View 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
View 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)

View 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

View 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
View 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)

View file

@ -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")