Merge remote-tracking branch 'origin/dev' into release-candidate

This commit is contained in:
Aleksandr Kutuzov 2023-02-14 00:44:31 +09:00
commit e2e4dcc2f8
17 changed files with 94 additions and 78 deletions

View File

@ -96,14 +96,14 @@ jobs:
- name: 'Upload map analyser files to storage' - name: 'Upload map analyser files to storage'
if: ${{ !github.event.pull_request.head.repo.fork }} if: ${{ !github.event.pull_request.head.repo.fork }}
uses: keithweaver/aws-s3-github-action@v1.0.0 uses: prewk/s3-cp-action@v2
with: with:
source: map_analyser_files/ aws_s3_endpoint: "${{ secrets.MAP_REPORT_AWS_ENDPOINT }}"
destination: "s3://${{ secrets.MAP_REPORT_AWS_BUCKET }}/${{steps.names.outputs.random_hash}}"
aws_access_key_id: "${{ secrets.MAP_REPORT_AWS_ACCESS_KEY }}" aws_access_key_id: "${{ secrets.MAP_REPORT_AWS_ACCESS_KEY }}"
aws_secret_access_key: "${{ secrets.MAP_REPORT_AWS_SECRET_KEY }}" aws_secret_access_key: "${{ secrets.MAP_REPORT_AWS_SECRET_KEY }}"
aws_region: "${{ secrets.MAP_REPORT_AWS_REGION }}" source: "./map_analyser_files/"
flags: --recursive dest: "s3://${{ secrets.MAP_REPORT_AWS_BUCKET }}/${{steps.names.outputs.random_hash}}"
flags: "--recursive --acl public-read"
- name: 'Trigger map file reporter' - name: 'Trigger map file reporter'
if: ${{ !github.event.pull_request.head.repo.fork }} if: ${{ !github.event.pull_request.head.repo.fork }}
@ -114,7 +114,6 @@ jobs:
event-type: map-file-analyse event-type: map-file-analyse
client-payload: '{"random_hash": "${{steps.names.outputs.random_hash}}", "event_type": "${{steps.names.outputs.event_type}}"}' client-payload: '{"random_hash": "${{steps.names.outputs.random_hash}}", "event_type": "${{steps.names.outputs.event_type}}"}'
- name: 'Upload artifacts to update server' - name: 'Upload artifacts to update server'
if: ${{ !github.event.pull_request.head.repo.fork }} if: ${{ !github.event.pull_request.head.repo.fork }}
run: | run: |

View File

@ -54,17 +54,16 @@ jobs:
./fbt COMPACT=1 PVSNOBROWSER=1 firmware_pvs || WARNINGS=1 ./fbt COMPACT=1 PVSNOBROWSER=1 firmware_pvs || WARNINGS=1
echo "warnings=${WARNINGS}" >> $GITHUB_OUTPUT echo "warnings=${WARNINGS}" >> $GITHUB_OUTPUT
- name: 'Upload artifacts to update server' - name: 'Upload report'
if: ${{ !github.event.pull_request.head.repo.fork && (steps.pvs-warn.outputs.warnings != 0) }} if: ${{ !github.event.pull_request.head.repo.fork && (steps.pvs-warn.outputs.warnings != 0) }}
run: | uses: prewk/s3-cp-action@v2
mkdir -p ~/.ssh with:
ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts aws_s3_endpoint: "${{ secrets.PVS_AWS_ENDPOINT }}"
echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key; aws_access_key_id: "${{ secrets.PVS_AWS_ACCESS_KEY }}"
chmod 600 ./deploy_key; aws_secret_access_key: "${{ secrets.PVS_AWS_SECRET_KEY }}"
rsync -avrzP --mkpath \ source: "./build/f7-firmware-DC/pvsreport"
-e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \ dest: "s3://${{ secrets.PVS_AWS_BUCKET }}/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/"
build/f7-firmware-DC/pvsreport/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:/home/data/firmware-pvs-studio-report/"${BRANCH_NAME}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/"; flags: "--recursive --acl public-read"
rm ./deploy_key;
- name: 'Find Previous Comment' - name: 'Find Previous Comment'
if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }} if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }}
@ -83,7 +82,7 @@ jobs:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
body: | body: |
**PVS-Studio report for commit `${{steps.names.outputs.commit_sha}}`:** **PVS-Studio report for commit `${{steps.names.outputs.commit_sha}}`:**
- [Report](https://update.flipperzero.one/builds/firmware-pvs-studio-report/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/index.html) - [Report](https://pvs.flipp.dev/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/index.html)
edit-mode: replace edit-mode: replace
- name: 'Raise exception' - name: 'Raise exception'

View File

@ -1,7 +1,7 @@
App( App(
appid="example_custom_font", appid="example_custom_font",
name="Example: custom font", name="Example: custom font",
apptype=FlipperAppType.EXTERNAL, apptype=FlipperAppType.DEBUG,
entry_point="example_custom_font_main", entry_point="example_custom_font_main",
requires=["gui"], requires=["gui"],
stack_size=1 * 1024, stack_size=1 * 1024,

View File

@ -115,8 +115,12 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
if(furi_hal_usb_is_locked()) { if(furi_hal_usb_is_locked()) {
app->error = BadUsbAppErrorCloseRpc; app->error = BadUsbAppErrorCloseRpc;
app->usb_if_prev = NULL;
scene_manager_next_scene(app->scene_manager, BadUsbSceneError); scene_manager_next_scene(app->scene_manager, BadUsbSceneError);
} else { } else {
app->usb_if_prev = furi_hal_usb_get_config();
furi_check(furi_hal_usb_set_config(NULL, NULL));
if(!furi_string_empty(app->file_path)) { if(!furi_string_empty(app->file_path)) {
app->bad_usb_script = bad_usb_script_open(app->file_path); app->bad_usb_script = bad_usb_script_open(app->file_path);
bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout); bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout);
@ -138,6 +142,10 @@ void bad_usb_app_free(BadUsbApp* app) {
app->bad_usb_script = NULL; app->bad_usb_script = NULL;
} }
if(app->usb_if_prev) {
furi_check(furi_hal_usb_set_config(app->usb_if_prev, NULL));
}
// Views // Views
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork); view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork);
bad_usb_free(app->bad_usb_view); bad_usb_free(app->bad_usb_view);

View File

@ -14,6 +14,7 @@
#include <gui/modules/variable_item_list.h> #include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h> #include <gui/modules/widget.h>
#include "views/bad_usb_view.h" #include "views/bad_usb_view.h"
#include <furi_hal_usb.h>
#define BAD_USB_APP_BASE_FOLDER ANY_PATH("badusb") #define BAD_USB_APP_BASE_FOLDER ANY_PATH("badusb")
#define BAD_USB_APP_PATH_LAYOUT_FOLDER BAD_USB_APP_BASE_FOLDER "/assets/layouts" #define BAD_USB_APP_PATH_LAYOUT_FOLDER BAD_USB_APP_BASE_FOLDER "/assets/layouts"
@ -39,6 +40,8 @@ struct BadUsbApp {
FuriString* keyboard_layout; FuriString* keyboard_layout;
BadUsb* bad_usb_view; BadUsb* bad_usb_view;
BadUsbScript* bad_usb_script; BadUsbScript* bad_usb_script;
FuriHalUsbInterface* usb_if_prev;
}; };
typedef enum { typedef enum {

View File

@ -490,8 +490,6 @@ static int32_t bad_usb_worker(void* context) {
BadUsbWorkerState worker_state = BadUsbStateInit; BadUsbWorkerState worker_state = BadUsbStateInit;
int32_t delay_val = 0; int32_t delay_val = 0;
FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
FURI_LOG_I(WORKER_TAG, "Init"); FURI_LOG_I(WORKER_TAG, "Init");
File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
bad_usb->line = furi_string_alloc(); bad_usb->line = furi_string_alloc();
@ -642,8 +640,6 @@ static int32_t bad_usb_worker(void* context) {
furi_hal_hid_set_state_callback(NULL, NULL); furi_hal_hid_set_state_callback(NULL, NULL);
furi_hal_usb_set_config(usb_mode_prev, NULL);
storage_file_close(script_file); storage_file_close(script_file);
storage_file_free(script_file); storage_file_free(script_file);
furi_string_free(bad_usb->line); furi_string_free(bad_usb->line);

View File

@ -26,7 +26,7 @@ void nfc_magic_scene_wrong_card_on_enter(void* context) {
AlignLeft, AlignLeft,
AlignTop, AlignTop,
FontSecondary, FontSecondary,
"Writing is supported\nonly for 4 bytes UID\nMifare CLassic 1k"); "Writing is supported\nonly for 4 bytes UID\nMifare Classic 1k");
widget_add_button_element( widget_add_button_element(
widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wrong_card_widget_callback, nfc_magic); widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wrong_card_widget_callback, nfc_magic);

View File

@ -58,8 +58,12 @@ bool updater_scene_error_on_event(void* context, SceneManagerEvent event) {
} }
void updater_scene_error_on_exit(void* context) { void updater_scene_error_on_exit(void* context) {
furi_assert(context);
Updater* updater = (Updater*)context; Updater* updater = (Updater*)context;
widget_reset(updater->widget); widget_reset(updater->widget);
free(updater->pending_update);
if(updater->loaded_manifest) {
update_manifest_free(updater->loaded_manifest);
}
} }

View File

@ -21,11 +21,9 @@ void updater_scene_loadcfg_apply_callback(GuiButtonType result, InputType type,
void updater_scene_loadcfg_on_enter(void* context) { void updater_scene_loadcfg_on_enter(void* context) {
Updater* updater = (Updater*)context; Updater* updater = (Updater*)context;
UpdaterManifestProcessingState* pending_upd = updater->pending_update = UpdateManifest* loaded_manifest = updater->loaded_manifest = update_manifest_alloc();
malloc(sizeof(UpdaterManifestProcessingState));
pending_upd->manifest = update_manifest_alloc();
if(update_manifest_init(pending_upd->manifest, furi_string_get_cstr(updater->startup_arg))) { if(update_manifest_init(loaded_manifest, furi_string_get_cstr(updater->startup_arg))) {
widget_add_string_element( widget_add_string_element(
updater->widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, "Update"); updater->widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, "Update");
@ -37,7 +35,7 @@ void updater_scene_loadcfg_on_enter(void* context) {
32, 32,
AlignCenter, AlignCenter,
AlignCenter, AlignCenter,
furi_string_get_cstr(pending_upd->manifest->version), furi_string_get_cstr(loaded_manifest->version),
true); true);
widget_add_button_element( widget_add_button_element(
@ -95,13 +93,12 @@ bool updater_scene_loadcfg_on_event(void* context, SceneManagerEvent event) {
} }
void updater_scene_loadcfg_on_exit(void* context) { void updater_scene_loadcfg_on_exit(void* context) {
furi_assert(context);
Updater* updater = (Updater*)context; Updater* updater = (Updater*)context;
if(updater->pending_update) {
update_manifest_free(updater->pending_update->manifest);
furi_string_free(updater->pending_update->message);
}
widget_reset(updater->widget); widget_reset(updater->widget);
free(updater->pending_update);
if(updater->loaded_manifest) {
update_manifest_free(updater->loaded_manifest);
}
} }

View File

@ -33,12 +33,6 @@ typedef enum {
UpdaterCustomEventSdUnmounted, UpdaterCustomEventSdUnmounted,
} UpdaterCustomEvent; } UpdaterCustomEvent;
typedef struct UpdaterManifestProcessingState {
UpdateManifest* manifest;
FuriString* message;
bool ready_to_be_applied;
} UpdaterManifestProcessingState;
typedef struct { typedef struct {
// GUI // GUI
Gui* gui; Gui* gui;
@ -49,7 +43,7 @@ typedef struct {
UpdaterMainView* main_view; UpdaterMainView* main_view;
UpdaterManifestProcessingState* pending_update; UpdateManifest* loaded_manifest;
UpdatePrepareResult preparation_result; UpdatePrepareResult preparation_result;
UpdateTask* update_task; UpdateTask* update_task;

View File

@ -41,22 +41,22 @@ typedef struct {
static const UpdateTaskStageGroupMap update_task_stage_progress[] = { static const UpdateTaskStageGroupMap update_task_stage_progress[] = {
[UpdateTaskStageProgress] = STAGE_DEF(UpdateTaskStageGroupMisc, 0), [UpdateTaskStageProgress] = STAGE_DEF(UpdateTaskStageGroupMisc, 0),
[UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), [UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 45),
[UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 15), [UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5),
[UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 15), [UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 15),
[UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 60), [UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 35),
[UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 80), [UpdateTaskStageRadioWrite] = STAGE_DEF(UpdateTaskStageGroupRadio, 60),
[UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 60), [UpdateTaskStageRadioInstall] = STAGE_DEF(UpdateTaskStageGroupRadio, 30),
[UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 80), [UpdateTaskStageRadioBusy] = STAGE_DEF(UpdateTaskStageGroupRadio, 5),
[UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 10), [UpdateTaskStageOBValidation] = STAGE_DEF(UpdateTaskStageGroupOptionBytes, 2),
[UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 50), [UpdateTaskStageValidateDFUImage] = STAGE_DEF(UpdateTaskStageGroupFirmware, 30),
[UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 200), [UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 150),
[UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 30), [UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 15),
[UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 30), [UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 5),
[UpdateTaskStageResourcesUpdate] = STAGE_DEF(UpdateTaskStageGroupResources, 255), [UpdateTaskStageResourcesUpdate] = STAGE_DEF(UpdateTaskStageGroupResources, 255),
[UpdateTaskStageSplashscreenInstall] = STAGE_DEF(UpdateTaskStageGroupSplashscreen, 5), [UpdateTaskStageSplashscreenInstall] = STAGE_DEF(UpdateTaskStageGroupSplashscreen, 5),

View File

@ -41,6 +41,14 @@ static bool update_task_pre_update(UpdateTask* update_task) {
return success; return success;
} }
typedef enum {
UpdateTaskResourcesWeightsFileCleanup = 20,
UpdateTaskResourcesWeightsDirCleanup = 20,
UpdateTaskResourcesWeightsFileUnpack = 60,
} UpdateTaskResourcesWeights;
#define UPDATE_TASK_RESOURCES_FILE_TO_TOTAL_PERCENT 90
typedef struct { typedef struct {
UpdateTask* update_task; UpdateTask* update_task;
int32_t total_files, processed_files; int32_t total_files, processed_files;
@ -54,33 +62,36 @@ static bool update_task_resource_unpack_cb(const char* name, bool is_directory,
update_task_set_progress( update_task_set_progress(
unpack_progress->update_task, unpack_progress->update_task,
UpdateTaskStageProgress, UpdateTaskStageProgress,
/* For this stage, last 70% of progress = extraction */ /* For this stage, last progress segment = extraction */
30 + (unpack_progress->processed_files * 70) / (unpack_progress->total_files + 1)); (UpdateTaskResourcesWeightsFileCleanup + UpdateTaskResourcesWeightsDirCleanup) +
(unpack_progress->processed_files * UpdateTaskResourcesWeightsFileUnpack) /
(unpack_progress->total_files + 1));
return true; return true;
} }
static void static void update_task_cleanup_resources(UpdateTask* update_task, const uint32_t n_tar_entries) {
update_task_cleanup_resources(UpdateTask* update_task, uint32_t n_approx_file_entries) {
ResourceManifestReader* manifest_reader = resource_manifest_reader_alloc(update_task->storage); ResourceManifestReader* manifest_reader = resource_manifest_reader_alloc(update_task->storage);
do { do {
FURI_LOG_I(TAG, "Cleaning up old manifest"); FURI_LOG_D(TAG, "Cleaning up old manifest");
if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("Manifest"))) { if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("Manifest"))) {
FURI_LOG_W(TAG, "No existing manifest"); FURI_LOG_W(TAG, "No existing manifest");
break; break;
} }
/* We got # of entries in TAR file. Approx 1/4th is dir entries, we skip them */ const uint32_t n_approx_file_entries =
n_approx_file_entries = n_approx_file_entries * 3 / 4 + 1; n_tar_entries * UPDATE_TASK_RESOURCES_FILE_TO_TOTAL_PERCENT / 100 + 1;
uint32_t n_processed_files = 0; uint32_t n_dir_entries = 1;
ResourceManifestEntry* entry_ptr = NULL; ResourceManifestEntry* entry_ptr = NULL;
uint32_t n_processed_entries = 0;
while((entry_ptr = resource_manifest_reader_next(manifest_reader))) { while((entry_ptr = resource_manifest_reader_next(manifest_reader))) {
if(entry_ptr->type == ResourceManifestEntryTypeFile) { if(entry_ptr->type == ResourceManifestEntryTypeFile) {
update_task_set_progress( update_task_set_progress(
update_task, update_task,
UpdateTaskStageProgress, UpdateTaskStageProgress,
/* For this stage, first 20% of progress = cleanup files */ /* For this stage, first pass = old manifest's file cleanup */
(n_processed_files++ * 20) / (n_approx_file_entries + 1)); (n_processed_entries++ * UpdateTaskResourcesWeightsFileCleanup) /
n_approx_file_entries);
FuriString* file_path = furi_string_alloc(); FuriString* file_path = furi_string_alloc();
path_concat( path_concat(
@ -88,16 +99,21 @@ static void
FURI_LOG_D(TAG, "Removing %s", furi_string_get_cstr(file_path)); FURI_LOG_D(TAG, "Removing %s", furi_string_get_cstr(file_path));
storage_simply_remove(update_task->storage, furi_string_get_cstr(file_path)); storage_simply_remove(update_task->storage, furi_string_get_cstr(file_path));
furi_string_free(file_path); furi_string_free(file_path);
} else if(entry_ptr->type == ResourceManifestEntryTypeDirectory) {
n_dir_entries++;
} }
} }
n_processed_entries = 0;
while((entry_ptr = resource_manifest_reader_previous(manifest_reader))) { while((entry_ptr = resource_manifest_reader_previous(manifest_reader))) {
if(entry_ptr->type == ResourceManifestEntryTypeDirectory) { if(entry_ptr->type == ResourceManifestEntryTypeDirectory) {
update_task_set_progress( update_task_set_progress(
update_task, update_task,
UpdateTaskStageProgress, UpdateTaskStageProgress,
/* For this stage, second 10% of progress = cleanup directories */ /* For this stage, second 10% of progress = cleanup directories */
(n_processed_files++ * 10) / (n_approx_file_entries + 1)); UpdateTaskResourcesWeightsFileCleanup +
(n_processed_entries++ * UpdateTaskResourcesWeightsDirCleanup) /
n_dir_entries);
FuriString* folder_path = furi_string_alloc(); FuriString* folder_path = furi_string_alloc();
File* folder_file = storage_file_alloc(update_task->storage); File* folder_file = storage_file_alloc(update_task->storage);

View File

@ -340,7 +340,7 @@ static void usb_process_mode_start(FuriHalUsbInterface* interface, void* context
} }
static void usb_process_mode_change(FuriHalUsbInterface* interface, void* context) { static void usb_process_mode_change(FuriHalUsbInterface* interface, void* context) {
if(interface != usb.interface) { if((interface != usb.interface) || (context != usb.interface_context)) {
if(usb.enabled) { if(usb.enabled) {
// Disable current interface // Disable current interface
susp_evt(&udev, 0, 0); susp_evt(&udev, 0, 0);

View File

@ -237,10 +237,8 @@ void ibutton_worker_emulate_timer_cb(void* context) {
const LevelDuration level_duration = const LevelDuration level_duration =
protocol_dict_encoder_yield(worker->protocols, worker->protocol_to_encode); protocol_dict_encoder_yield(worker->protocols, worker->protocol_to_encode);
const bool level = level_duration_get_level(level_duration); furi_hal_ibutton_emulate_set_next(level_duration_get_duration(level_duration));
furi_hal_ibutton_pin_write(level_duration_get_level(level_duration));
furi_hal_ibutton_emulate_set_next(level);
furi_hal_ibutton_pin_write(level);
} }
void ibutton_worker_emulate_timer_start(iButtonWorker* worker) { void ibutton_worker_emulate_timer_start(iButtonWorker* worker) {

View File

@ -106,6 +106,7 @@ void tar_archive_set_file_callback(TarArchive* archive, tar_unpack_file_cb callb
static int tar_archive_entry_counter(mtar_t* tar, const mtar_header_t* header, void* param) { static int tar_archive_entry_counter(mtar_t* tar, const mtar_header_t* header, void* param) {
UNUSED(tar); UNUSED(tar);
UNUSED(header); UNUSED(header);
furi_assert(param);
int32_t* counter = param; int32_t* counter = param;
(*counter)++; (*counter)++;
return 0; return 0;

View File

@ -1,6 +1,6 @@
from SCons.Builder import Builder from SCons.Builder import Builder
from SCons.Action import Action from SCons.Action import Action
from SCons.Script import Delete, Mkdir, GetBuildFailures from SCons.Script import Delete, Mkdir, GetBuildFailures, Flatten
import multiprocessing import multiprocessing
import webbrowser import webbrowser
import atexit import atexit
@ -30,12 +30,13 @@ def atexist_handler():
return return
for bf in GetBuildFailures(): for bf in GetBuildFailures():
if bf.node.exists and bf.node.name.endswith(".html"): for node in Flatten(bf.node):
if node.exists and node.name.endswith(".html"):
# macOS # macOS
if sys.platform == "darwin": if sys.platform == "darwin":
subprocess.run(["open", bf.node.abspath]) subprocess.run(["open", node.abspath])
else: else:
webbrowser.open(bf.node.abspath) webbrowser.open(node.abspath)
break break

View File

@ -17,7 +17,7 @@ def parse_args():
def checkCommitMessage(msg): def checkCommitMessage(msg):
regex = re.compile(r"^'?\[FL-\d+\]") regex = re.compile(r"^'?\[(FL-\d+,?\s?)+\]")
if regex.match(msg): if regex.match(msg):
return True return True
return False return False