Skip to content

Commit 8b0ee58

Browse files
committed
feat: accès au BuildContext pour les plugins et ajout du plugin OutputCleaner
- Ajout du champ build_context dans PreCompileContext pour exposer les données de compilation. - Mise à jour du moteur BCASL (Base, Loader, executor) pour transmettre le contexte. - Refonte des pipelines CLI et GUI pour générer le BuildContext avant l'exécution de BCASL. - Implémentation du plugin OutputCleaner utilisant le dossier de sortie du contexte. - Correction de la définition de PreCompileContext dans le SDK pour la compatibilité.
1 parent b9bcfe0 commit 8b0ee58

10 files changed

Lines changed: 204 additions & 23 deletions

File tree

Plugins/OutputCleaner/__init__.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# Copyright 2026 Ague Samuel Amen
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
import shutil
17+
from pathlib import Path
18+
from typing import Optional
19+
20+
from bcasl import bc_register
21+
from Plugins_SDK.BcPluginContext import BcPluginBase, PluginMeta, PreCompileContext
22+
from Plugins_SDK.GeneralContext import (
23+
Dialog,
24+
get_language_code,
25+
load_plugin_language_file,
26+
register_i18n_handler,
27+
register_plugin_translations,
28+
translate,
29+
)
30+
31+
# Create instances of Dialog for logging and user interaction
32+
log = Dialog()
33+
dialog = Dialog()
34+
35+
36+
def _load_i18n() -> None:
37+
try:
38+
lang_code = get_language_code()
39+
data = load_plugin_language_file(__package__, lang_code)
40+
if isinstance(data, dict) and data:
41+
register_plugin_translations("outputcleaner", data)
42+
except Exception:
43+
pass
44+
45+
46+
# Load translations now and refresh on language changes
47+
_load_i18n()
48+
try:
49+
register_i18n_handler(lambda _gui, _tr: _load_i18n())
50+
except Exception:
51+
pass
52+
53+
# Plugin metadata
54+
PLUGIN_META = PluginMeta(
55+
id="outputcleaner",
56+
name="Output Cleaner",
57+
version="1.0.0",
58+
description="Clean the output directory before compilation",
59+
author="Samuel Amen Ague",
60+
tags=["clean", "output"],
61+
required_bcasl_version="1.0.0",
62+
)
63+
64+
65+
@bc_register
66+
class OutputCleaner(BcPluginBase):
67+
"""Plugin de nettoyage du dossier output avant compilation.
68+
69+
Utilise le BuildContext pour identifier le dossier de sortie.
70+
"""
71+
72+
meta = PLUGIN_META
73+
74+
def __init__(self):
75+
super().__init__(meta=PLUGIN_META)
76+
77+
def _get_config(self, ctx: PreCompileContext) -> dict:
78+
try:
79+
plugins_cfg = ctx.config.get("plugins", {})
80+
entry = plugins_cfg.get(self.meta.id, {})
81+
return entry.get("config", {}) if isinstance(entry, dict) else {}
82+
except Exception:
83+
return {}
84+
85+
def on_pre_compile(self, ctx: PreCompileContext) -> None:
86+
"""Nettoie le dossier output avant la compilation."""
87+
try:
88+
if not ctx.build_context:
89+
log.log_warn("OutputCleaner: No BuildContext available. Cannot identify output directory.")
90+
return
91+
92+
output_dir_str = getattr(ctx.build_context, "output_dir", None)
93+
if not output_dir_str:
94+
log.log_warn("OutputCleaner: No output_dir defined in BuildContext.")
95+
return
96+
97+
output_dir = Path(output_dir_str)
98+
if not output_dir.is_absolute():
99+
output_dir = ctx.root / output_dir
100+
101+
if not output_dir.exists():
102+
log.log_info(f"OutputCleaner: Output directory does not exist: {output_dir}")
103+
return
104+
105+
log.log_info(f"OutputCleaner: Cleaning output directory: {output_dir}")
106+
107+
# Simple confirmation if configured
108+
cfg = self._get_config(ctx)
109+
if bool(cfg.get("confirm", False)):
110+
response = dialog.msg_question(
111+
title="Output Cleaner",
112+
text=f"Do you want to delete all contents in {output_dir}?",
113+
default_yes=True,
114+
)
115+
if not response:
116+
return
117+
118+
# Actually delete the directory and recreate it
119+
try:
120+
shutil.rmtree(output_dir)
121+
output_dir.mkdir(parents=True, exist_ok=True)
122+
log.log_info(f"OutputCleaner: Successfully cleaned {output_dir}")
123+
except Exception as e:
124+
log.log_err(f"OutputCleaner: Failed to clean {output_dir}: {e}")
125+
126+
except Exception as e:
127+
log.log_err(f"OutputCleaner error: {e}")

Plugins_SDK/BcPluginContext/Context.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,12 @@ class BcPluginBase: # type: ignore
6161
class PluginMeta: # type: ignore
6262
pass
6363

64+
@dataclass
6465
class PreCompileContext:
65-
pass
66+
root: Path
67+
config: dict[str, Any] = field(default_factory=dict)
68+
metadata: dict[str, Any] = field(default_factory=dict)
69+
build_context: Optional[Any] = None
6670

6771

6872
def register_plugin(cls: Any) -> Any: # type: ignore

Ui/Cli/app.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,6 @@ def _build_impl(
9090
if not as_json and verbose:
9191
info(f"Workspace: {workspace}")
9292

93-
# 1. BCASL Pre-compile check (Point 1 of mutation plan)
94-
from .helpers import run_bcasl_before_compile_sync
95-
96-
if not run_bcasl_before_compile_sync(workspace, verbose=verbose):
97-
return 1
98-
9993
if lock_file is None:
10094
if not as_json and verbose:
10195
info("Loading ark.yml...")
@@ -117,6 +111,11 @@ def _build_impl(
117111
lock_paths = write_lock_files(workspace, lock_payload)
118112
context = build_context_object_from_ark_config(validated.config)
119113

114+
# 1. BCASL Pre-compile check (Point 1 of mutation plan)
115+
from .helpers import run_bcasl_before_compile_sync
116+
if not run_bcasl_before_compile_sync(workspace, verbose=verbose, build_context=context):
117+
return 1
118+
120119
if not as_json and verbose:
121120
info("Starting engine compilation...")
122121
result = run_engine_compile(
@@ -173,6 +172,12 @@ def _build_impl(
173172
if not as_json and verbose:
174173
info(f"Building from lock with engine '{engine_id}'...")
175174
context = build_context_object_from_lock(lock_payload)
175+
176+
# BCASL Pre-compile check for lock branch
177+
from .helpers import run_bcasl_before_compile_sync
178+
if not run_bcasl_before_compile_sync(workspace, verbose=verbose, build_context=context):
179+
return 1
180+
176181
result = run_engine_compile(
177182
workspace=workspace,
178183
engine_id=engine_id,

Ui/Cli/helpers.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from contextlib import contextmanager
1111
from dataclasses import dataclass
1212
from pathlib import Path
13-
from typing import Any
13+
from typing import Any, Optional
1414

1515
# Global event for CLI cancellation
1616
_CLI_CANCEL_EVENT = threading.Event()
@@ -471,7 +471,9 @@ def scaffold_plugin_payload(name: str, root_dir: str | None = None) -> dict[str,
471471
return scaffold_plugin(name, root_dir=root_dir)
472472

473473

474-
def run_bcasl_before_compile_sync(workspace: Path, verbose: bool = False) -> bool:
474+
def run_bcasl_before_compile_sync(
475+
workspace: Path, verbose: bool = False, build_context: Optional[Any] = None
476+
) -> bool:
475477
"""Run BCASL pre-compile stage synchronously for the CLI.
476478
477479
Returns:
@@ -528,7 +530,7 @@ def append(self, msg: str):
528530
try:
529531
# Catch direct prints from BCASL plugins or loader
530532
with _redirect_output(not verbose):
531-
report = run_pre_compile(host)
533+
report = run_pre_compile(host, build_context=build_context)
532534
except Exception as exc:
533535
if status:
534536
status.stop()

Ui/Gui/Compilation/helpers.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
from __future__ import annotations
2323

24-
from typing import Optional
24+
from typing import Any, Optional
2525

2626
from Ui.Gui.Compilation.mainprocess import MainProcess
2727
from Ui.i18n import log_i18n_level
@@ -51,7 +51,9 @@ def resolve_default_engine_id() -> str:
5151
return "engine"
5252

5353

54-
def run_bcasl_before_compile(gui_instance, on_done) -> None:
54+
def run_bcasl_before_compile(
55+
gui_instance, on_done, build_context: Optional[Any] = None
56+
) -> None:
5557
"""Run BCASL pre-compile stage, then invoke `on_done(report)`."""
5658
try:
5759
from bcasl import run_pre_compile_async
@@ -72,7 +74,7 @@ def run_bcasl_before_compile(gui_instance, on_done) -> None:
7274
except Exception:
7375
pass
7476
try:
75-
run_pre_compile_async(gui_instance, on_done)
77+
run_pre_compile_async(gui_instance, on_done, build_context=build_context)
7678
except Exception:
7779
if callable(on_done):
7880
try:

Ui/Gui/Dialogs/BcaslDialog.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,12 +856,14 @@ def __init__(
856856
Plugins_dir: "Path",
857857
cfg: "dict[str, Any]",
858858
plugin_timeout: float,
859+
build_context: Optional[Any] = None,
859860
) -> None:
860861
super().__init__()
861862
self.workspace_root = workspace_root
862863
self.Plugins_dir = Plugins_dir
863864
self.cfg = cfg
864865
self.plugin_timeout = plugin_timeout
866+
self.build_context = build_context
865867
self._cancel_requested = False
866868

867869
def request_cancel(self) -> None:
@@ -882,6 +884,7 @@ def run(self) -> None:
882884
self.plugin_timeout,
883885
log_cb=self.log.emit,
884886
stop_requested=lambda: bool(self._cancel_requested),
887+
build_context=self.build_context,
885888
)
886889
self.finished.emit(report)
887890
except Exception as e:

Ui/Gui/Dialogs/CompilerDialog.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ def _after_bcasl(_report=None) -> None:
391391
f"Compilation start error: {e}",
392392
)
393393

394-
run_bcasl_before_compile(self, _after_bcasl)
394+
run_bcasl_before_compile(self, _after_bcasl, build_context=context)
395395

396396

397397
def rebuild_from_lock(self, lock_path: Path) -> None:
@@ -679,7 +679,7 @@ def _after_bcasl(_report=None) -> None:
679679
self.set_controls_enabled(True)
680680
result["value"] = ok
681681

682-
run_bcasl_before_compile(self, _after_bcasl)
682+
run_bcasl_before_compile(self, _after_bcasl, build_context=context)
683683
if result["value"] is not None:
684684
return bool(result["value"])
685685
return True

bcasl/Base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ class PreCompileContext:
9191
root: Path
9292
config: dict[str, Any] = field(default_factory=dict)
9393
metadata: dict[str, Any] = field(default_factory=dict)
94+
build_context: Optional[Any] = None
9495
_iter_cache: dict[tuple[tuple[str, ...], tuple[str, ...]], list[Path]] = field(
9596
default_factory=dict, repr=False, compare=False
9697
)

bcasl/Loader.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -309,9 +309,15 @@ def _run_bcasl_sync(
309309
plugin_timeout: float,
310310
log_cb: Optional[callable] = None,
311311
stop_requested: Optional[callable] = None,
312+
build_context: Optional[Any] = None,
312313
):
313314
"""Execute BCASL en mode synchrone et return le rapport."""
314-
manager = BCASL(workspace_root, config=cfg, plugin_timeout_s=plugin_timeout)
315+
manager = BCASL(
316+
workspace_root,
317+
config=cfg,
318+
plugin_timeout_s=plugin_timeout,
319+
build_context=build_context,
320+
)
315321
loaded, errors = manager.load_plugins_from_directory(plugins_dir)
316322
_emit_log(log_cb, f"BCASL: {loaded} package(s) chargé(s) depuis Plugins/\n")
317323
for mod, msg in errors or []:
@@ -322,7 +328,10 @@ def _run_bcasl_sync(
322328
workspace_meta = _build_workspace_meta(workspace_root, cfg)
323329
return manager.run_pre_compile(
324330
PreCompileContext(
325-
root=workspace_root, config=cfg, metadata=workspace_meta
331+
root=workspace_root,
332+
config=cfg,
333+
metadata=workspace_meta,
334+
build_context=build_context,
326335
),
327336
stop_requested=stop_requested,
328337
log_cb=log_cb,
@@ -620,7 +629,9 @@ def open_bc_loader_dialog(self) -> None:
620629
# Plugins
621630

622631

623-
def run_pre_compile_async(self, on_done: Optional[callable] = None) -> None:
632+
def run_pre_compile_async(
633+
self, on_done: Optional[callable] = None, build_context: Optional[Any] = None
634+
) -> None:
624635
"""Lance BCASL en arrière-plan si QtCore est dispo; sinon, exécution bloquante rPluginsde.
625636
on_done(report) appelé à la fin si fourni.
626637
"""
@@ -661,7 +672,13 @@ def run_pre_compile_async(self, on_done: Optional[callable] = None) -> None:
661672
from Ui.Gui.Dialogs.BcaslDialog import _BCASLUiBridge, _BCASLWorker
662673

663674
thread = QThread()
664-
worker = _BCASLWorker(workspace_root, Plugins_dir, cfg, plugin_timeout)
675+
worker = _BCASLWorker(
676+
workspace_root,
677+
Plugins_dir,
678+
cfg,
679+
plugin_timeout,
680+
build_context=build_context,
681+
)
665682
try:
666683
self._bcasl_thread = thread
667684
self._bcasl_worker = worker
@@ -694,6 +711,7 @@ def run_pre_compile_async(self, on_done: Optional[callable] = None) -> None:
694711
cfg,
695712
plugin_timeout,
696713
log_cb=log_cb,
714+
build_context=build_context,
697715
)
698716
except Exception as _e:
699717
report = None
@@ -720,7 +738,7 @@ def run_pre_compile_async(self, on_done: Optional[callable] = None) -> None:
720738
pass
721739

722740

723-
def run_pre_compile(self) -> Optional[object]:
741+
def run_pre_compile(self, build_context: Optional[Any] = None) -> Optional[object]:
724742
"""Execute la phase BCASL de pre-compilation (path synchrone, simple)."""
725743
try:
726744
if not getattr(self, "workspace_dir", None):
@@ -749,6 +767,7 @@ def run_pre_compile(self) -> Optional[object]:
749767
cfg,
750768
plugin_timeout,
751769
log_cb=log_cb,
770+
build_context=build_context,
752771
)
753772
if hasattr(self, "log") and self.log is not None:
754773
self.log.append("BCASL - Rapport:\n")

0 commit comments

Comments
 (0)