@@ -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 :
0 commit comments