Skip to content

Commit 0067698

Browse files
author
PyCompiler ARK++ Team
committed
feat(bcasl): ajout du toggle global Activer/Desactiver; respect options.enabled en runtime\nfeat(cx_freeze): robustesse target-dir (création auto), normalisation chemins, synchro UI; checkbox encodings\nchore: alignement ACASL ouverture dossier de sortie par orchestrateur
1 parent 34a9898 commit 0067698

2 files changed

Lines changed: 212 additions & 3 deletions

File tree

ENGINES/cx_freeze/engine.py

Lines changed: 148 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,91 @@ def _resolve_venv_root(self, gui) -> Optional[str]:
2222
def _pip_exe(self, vroot: str) -> str:
2323
return pip_executable(vroot)
2424

25+
def _log(self, gui, fr: str, en: str) -> None:
26+
try:
27+
gui.log.append(gui.tr(fr, en))
28+
except Exception:
29+
pass
30+
31+
def _norm_path(self, gui, p: str) -> str:
32+
try:
33+
s = str(p).strip()
34+
except Exception:
35+
s = p
36+
try:
37+
s = os.path.expanduser(s)
38+
except Exception:
39+
pass
40+
if not os.path.isabs(s):
41+
try:
42+
base = getattr(gui, "workspace_dir", None)
43+
if base:
44+
s = os.path.join(base, s)
45+
except Exception:
46+
pass
47+
try:
48+
s = os.path.abspath(s)
49+
except Exception:
50+
pass
51+
return s
52+
53+
def _dedupe_args(self, seq: list[str]) -> list[str]:
54+
seen = set()
55+
out: list[str] = []
56+
for x in seq:
57+
if x not in seen:
58+
out.append(x)
59+
seen.add(x)
60+
return out
61+
62+
def _ensure_output_dir(self, gui, p: str) -> str:
63+
"""Normalize and ensure the output directory exists. Fallback to workspace/dist when needed."""
64+
try:
65+
ws = getattr(gui, "workspace_dir", None) or os.getcwd()
66+
except Exception:
67+
ws = os.getcwd()
68+
try:
69+
target = self._norm_path(gui, p or "")
70+
except Exception:
71+
target = os.path.join(ws, "dist")
72+
# If path points to a file, use its parent
73+
try:
74+
if os.path.isfile(target):
75+
self._log(
76+
gui,
77+
f"⚠️ Le chemin de sortie pointe vers un fichier: {target}. Utilisation du dossier parent.",
78+
f"⚠️ Output path points to a file: {target}. Using parent directory.",
79+
)
80+
target = os.path.dirname(target) or os.path.join(ws, "dist")
81+
except Exception:
82+
pass
83+
# Try to create
84+
try:
85+
os.makedirs(target, exist_ok=True)
86+
return target
87+
except Exception as e:
88+
self._log(
89+
gui,
90+
f"⚠️ Impossible de créer le dossier de sortie '{target}': {e}. Utilisation de workspace/dist.",
91+
f"⚠️ Failed to create output directory '{target}': {e}. Using workspace/dist.",
92+
)
93+
fallback = os.path.join(ws, "dist")
94+
try:
95+
os.makedirs(fallback, exist_ok=True)
96+
return fallback
97+
except Exception as e2:
98+
self._log(
99+
gui,
100+
f"❌ Échec de création du dossier fallback '{fallback}': {e2}",
101+
f"❌ Failed to create fallback directory '{fallback}': {e2}",
102+
)
103+
last = os.path.join(os.getcwd(), "dist")
104+
try:
105+
os.makedirs(last, exist_ok=True)
106+
except Exception:
107+
pass
108+
return last
109+
25110
def _ensure_tool_with_pip(self, gui, vroot: str, package: str) -> bool:
26111
pip = self._pip_exe(vroot)
27112
try:
@@ -397,6 +482,14 @@ def build_command(self, gui, file: str) -> list[str]:
397482
except Exception:
398483
base = getattr(gui, "workspace_dir", None) or os.getcwd()
399484
out_dir = os.path.join(base, "dist")
485+
out_dir = self._ensure_output_dir(gui, out_dir)
486+
try:
487+
if getattr(self, "_output_dir_input", None) is not None:
488+
self._output_dir_input.setText(out_dir)
489+
if hasattr(gui, "output_dir_input") and getattr(gui, "output_dir_input", None):
490+
gui.output_dir_input.setText(out_dir)
491+
except Exception:
492+
pass
400493
cmd = [sys.executable, "-m", "cx_Freeze", file, "--target-dir", out_dir]
401494
extra: list[str] = []
402495
# Apply base (Windows only for Win32GUI)
@@ -438,7 +531,14 @@ def build_command(self, gui, file: str) -> list[str]:
438531
try:
439532
p = self._icon_edit.text().strip() if hasattr(self, "_icon_edit") and self._icon_edit else ""
440533
if p:
441-
extra += ["--icon", p]
534+
try:
535+
p_norm = self._norm_path(gui, p)
536+
except Exception:
537+
p_norm = p
538+
if os.path.isfile(p_norm):
539+
extra += ["--icon", p_norm]
540+
else:
541+
self._log(gui, f"⚠️ Icône introuvable: {p_norm}", f"⚠️ Icon not found: {p_norm}")
442542
except Exception:
443543
pass
444544
# Target name
@@ -537,11 +637,26 @@ def build_command(self, gui, file: str) -> list[str]:
537637
auto_args = _ap.compute_auto_for_engine(gui, "cx_freeze") or []
538638
except Exception:
539639
auto_args = []
640+
try:
641+
extra = self._dedupe_args(extra)
642+
except Exception:
643+
pass
540644
return cmd + extra + auto_args
541645

542646
def program_and_args(self, gui, file: str) -> Optional[tuple[str, list[str]]]:
543647
# Resolve cxfreeze (or python -m cx_Freeze) in venv; we'll prefer module form using python from venv
544648
try:
649+
# Validate script path
650+
try:
651+
if not os.path.isfile(file):
652+
try:
653+
gui.log.append(gui.tr("❌ Script introuvable: ", "❌ Script not found: ") + str(file))
654+
gui.show_error_dialog(os.path.basename(file))
655+
except Exception:
656+
pass
657+
return None
658+
except Exception:
659+
pass
545660
vm = getattr(gui, "venv_manager", None)
546661
vroot = vm.resolve_project_venv() if vm else None
547662
if not vroot:
@@ -562,6 +677,16 @@ def program_and_args(self, gui, file: str) -> Optional[tuple[str, list[str]]]:
562677
# Replace sys.executable with python_exe from venv
563678
if cmd and (cmd[0].endswith("python") or cmd[0].endswith("python.exe")):
564679
cmd[0] = python_exe
680+
# Ensure target directory exists and normalize it
681+
try:
682+
if "--target-dir" in cmd:
683+
idx = cmd.index("--target-dir")
684+
if idx + 1 < len(cmd):
685+
td = cmd[idx + 1]
686+
td_norm = self._ensure_output_dir(gui, td)
687+
cmd[idx + 1] = td_norm
688+
except Exception:
689+
pass
565690
return python_exe, cmd[1:]
566691
except Exception:
567692
return None
@@ -654,14 +779,31 @@ def create_tab(self, gui):
654779
self._cx_out_browse_btn = browse_btn
655780

656781
def _browse():
782+
# Open dialog with a sensible default directory and sync global output dir
657783
try:
784+
start_dir = ""
785+
try:
786+
cur = out_edit.text().strip()
787+
if cur:
788+
start_dir = cur
789+
else:
790+
ws = getattr(gui, "workspace_dir", None)
791+
if ws:
792+
start_dir = ws
793+
except Exception:
794+
start_dir = ""
658795
d = QFileDialog.getExistingDirectory(
659-
tab, gui.tr("Choisir le dossier de sortie", "Choose output directory"), ""
796+
tab, gui.tr("Choisir le dossier de sortie", "Choose output directory"), start_dir
660797
)
661798
except Exception:
662-
d = QFileDialog.getExistingDirectory(tab, "Choose output directory", "")
799+
d = QFileDialog.getExistingDirectory(tab, "Choose output directory", start_dir if 'start_dir' in locals() else "")
663800
if d:
664801
out_edit.setText(d)
802+
try:
803+
if hasattr(gui, "output_dir_input") and gui.output_dir_input:
804+
gui.output_dir_input.setText(d)
805+
except Exception:
806+
pass
665807

666808
try:
667809
browse_btn.clicked.connect(_browse)
@@ -703,15 +845,18 @@ def _browse():
703845
cb_deps.setChecked(True)
704846
except Exception:
705847
pass
848+
cb_enc = QCheckBox(gui.tr("Inclure encodings", "Include encodings"))
706849
row2.addWidget(base_label)
707850
row2.addWidget(base_combo)
708851
row2.addStretch(1)
709852
row2.addWidget(cb_deps)
853+
row2.addWidget(cb_enc)
710854
row2.addStretch(1)
711855
layout.addLayout(row2)
712856
self._base_label = base_label
713857
self._base_combo = base_combo
714858
self._cb_include_deps = cb_deps
859+
self._cb_enc = cb_enc
715860
# Icon picker
716861
row3 = QHBoxLayout()
717862
try:

bcasl/bcasl_loader.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,7 @@ def _invoke():
986986
QDialog,
987987
QHBoxLayout,
988988
QLabel,
989+
QCheckBox,
989990
QListWidget,
990991
QListWidgetItem,
991992
QMessageBox,
@@ -1184,6 +1185,15 @@ def open_api_loader_dialog(self) -> None:
11841185
)
11851186
)
11861187
layout.addWidget(info)
1188+
# Global BCASL enable/disable
1189+
chk_enable = QCheckBox("Activer BCASL / Enable BCASL", dlg)
1190+
try:
1191+
opt = cfg.get("options", {}) if isinstance(cfg, dict) else {}
1192+
bcasl_enabled_flag = bool(opt.get("enabled", True)) if isinstance(opt, dict) else True
1193+
except Exception:
1194+
bcasl_enabled_flag = True
1195+
chk_enable.setChecked(bcasl_enabled_flag)
1196+
layout.addWidget(chk_enable)
11871197
# Liste réordonnable par glisser-déposer, avec cases à cocher
11881198
lst = QListWidget(dlg)
11891199
lst.setSelectionMode(QAbstractItemView.SingleSelection)
@@ -1594,6 +1604,22 @@ def _move_sel(delta: int):
15941604
btns.addWidget(btn_save)
15951605
layout.addLayout(btns)
15961606

1607+
# Enable/disable list and move buttons based on global toggle
1608+
def _apply_enabled_state():
1609+
en = chk_enable.isChecked()
1610+
try:
1611+
lst.setEnabled(en)
1612+
btn_up.setEnabled(en)
1613+
btn_down.setEnabled(en)
1614+
except Exception:
1615+
pass
1616+
1617+
try:
1618+
chk_enable.toggled.connect(lambda _=None: _apply_enabled_state())
1619+
_apply_enabled_state()
1620+
except Exception:
1621+
pass
1622+
15971623
def do_save():
15981624
# Extraire ordre et états depuis la QListWidget
15991625
new_plugins: dict[str, Any] = {}
@@ -1611,6 +1637,13 @@ def do_save():
16111637
cfg_out = {}
16121638
cfg_out["plugins"] = new_plugins
16131639
cfg_out["plugin_order"] = order_ids
1640+
# Global enabled flag in options
1641+
try:
1642+
opts = cfg_out.get("options", {}) if isinstance(cfg_out.get("options"), dict) else {}
1643+
opts["enabled"] = bool(chk_enable.isChecked())
1644+
cfg_out["options"] = opts
1645+
except Exception:
1646+
pass
16141647
# Écrire bcasl.json (toujours JSON, priorité sur autres formats)
16151648
target = workspace_root / "bcasl.json"
16161649
try:
@@ -1725,6 +1758,24 @@ def run_pre_compile_async(self, on_done: Optional[callable] = None) -> None:
17251758
cfg_timeout = 0.0
17261759
plugin_timeout_raw = cfg_timeout if cfg_timeout != 0.0 else env_timeout
17271760
plugin_timeout = plugin_timeout_raw if plugin_timeout_raw and plugin_timeout_raw > 0 else 0.0
1761+
# Respect global enabled flag: skip BCASL when disabled
1762+
try:
1763+
opt = cfg.get("options", {}) if isinstance(cfg, dict) else {}
1764+
bcasl_enabled = bool(opt.get("enabled", True)) if isinstance(opt, dict) else True
1765+
except Exception:
1766+
bcasl_enabled = True
1767+
if not bcasl_enabled:
1768+
try:
1769+
if hasattr(self, "log") and self.log is not None:
1770+
self.log.append(self.tr("⏹️ BCASL désactivé dans la configuration. Exécution ignorée\n", "⏹️ BCASL disabled in configuration. Skipping execution\n"))
1771+
except Exception:
1772+
pass
1773+
if callable(on_done):
1774+
try:
1775+
on_done({"status": "disabled"})
1776+
except Exception:
1777+
pass
1778+
return
17281779
# Lancer le worker thread (aucune boucle imbriquée)
17291780
from PySide6.QtCore import QTimer
17301781

@@ -1948,6 +1999,19 @@ def run_pre_compile(self) -> Optional[object]:
19481999
cfg_timeout = 0.0
19492000
plugin_timeout_raw = cfg_timeout if cfg_timeout != 0.0 else env_timeout
19502001
plugin_timeout = plugin_timeout_raw if plugin_timeout_raw and plugin_timeout_raw > 0 else 0.0
2002+
# Respect global enabled flag: skip BCASL when disabled
2003+
try:
2004+
opt = cfg.get("options", {}) if isinstance(cfg, dict) else {}
2005+
bcasl_enabled = bool(opt.get("enabled", True)) if isinstance(opt, dict) else True
2006+
except Exception:
2007+
bcasl_enabled = True
2008+
if not bcasl_enabled:
2009+
try:
2010+
if hasattr(self, "log") and self.log is not None:
2011+
self.log.append("⏹️ BCASL désactivé dans la configuration. Exécution ignorée\n")
2012+
except Exception:
2013+
pass
2014+
return None
19512015
# Run BCASL in background to avoid blocking UI when plugins use no progress UI
19522016
if QThread is not None and QEventLoop is not None and "_BCASLWorker" in globals():
19532017
try:

0 commit comments

Comments
 (0)