# -*- coding: utf-8 -*- """ builder.py - Handles the execution of the PyInstaller build process in a separate thread. """ import subprocess import threading import queue import os import traceback # For logging exceptions # NO CHANGES NEEDED in this file for the restructuring - EXCEPT FOR THE DEBUG LOG LINE ADDED BELOW def run_build_in_thread(command, working_dir, output_queue, logger_func, output_dir_name, environment=None): """ Executes the PyInstaller command using subprocess.Popen in the background. Streams stdout/stderr to the output_queue for display in the GUI. Notifies the main thread of success, failure, or completion via the queue. Args: command (list): The PyInstaller command and its arguments as a list of strings. working_dir (str): The directory from which to run the command (project root). output_queue (queue.Queue): Queue to send log messages and status updates. logger_func (function): Function from the GUI to log messages (also used by this thread). output_dir_name (str): The name of the dist/output directory (e.g., '_dist'). environment (dict, optional): A custom environment for the subprocess. Defaults to None (inherited). """ build_process = None try: logger_func("Build thread starting execution...", level="INFO") # --- MODIFICA INIZIO --- # Loggare il comando esatto che sta per essere eseguito per debugging # Questo ci aiuterà a vedere se --clean è presente o meno. # Usiamo una formattazione che gestisca gli spazi negli argomenti per una migliore leggibilità del log. command_str_for_log = " ".join([f'"{c}"' if " " in c else c for c in command]) logger_func(f"PyInstaller command to be executed: {command_str_for_log}", level="DEBUG") logger_func(f"Working directory: {working_dir}", level="DEBUG") # --- MODIFICA FINE --- env_log_msg = f"Using {'custom' if environment else 'inherited'} environment." if environment: pass # Placeholder, si potrebbe loggare di più sull'ambiente se necessario logger_func(f"PyInstaller process starting. {env_log_msg}", level="INFO") build_process = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, # Redirect stderr to stdout cwd=working_dir, text=True, # Decode output as text encoding='utf-8', errors='replace', # Handle potential decoding errors gracefully bufsize=1, # Line-buffered env=environment # Pass the custom or inherited environment ) logger_func(f"PyInstaller process launched (PID: {build_process.pid}).", level="INFO") # Stream output while True: try: line = build_process.stdout.readline() if not line: # Check if process has ended if build_process.poll() is not None: logger_func("End of PyInstaller output stream.", level="DEBUG") break else: # Process might still be running but not producing output momentarily # Could add a small sleep here if CPU usage is a concern, # but for typical PyInstaller builds, continuous checking is fine. pass else: output_queue.put(("LOG", line)) # Send line to GUI for display except Exception as read_err: # This might happen if the stream is closed unexpectedly logger_func(f"Error reading PyInstaller output stream: {read_err}\n{traceback.format_exc()}", level="ERROR") break return_code = build_process.wait() # Wait for the process to complete logger_func(f"PyInstaller process finished with exit code: {return_code}", level="INFO") if return_code == 0: # Check if the expected output directory/file exists dist_path_abs = os.path.join(working_dir, output_dir_name) # output_dir_name is like '_dist' # Note: For one-file, the output is a file, not a directory directly under dist_path_abs # For one-dir, the output is a directory. # We check for the existence of the dist_path_abs itself. # A more robust check might involve looking for the executable name inside dist_path_abs. if os.path.exists(dist_path_abs): # This checks if '_dist' (or equivalent) exists success_msg = f"Build completed successfully!\nOutput Directory: {dist_path_abs}" output_queue.put(("BUILD_SUCCESS", success_msg)) else: # This case might be rare if PyInstaller exits with 0, but good for robustness warn_msg = (f"Build finished with exit code 0, but output directory not found:\n" f"{dist_path_abs}\nCheck the full log for potential issues.") logger_func(warn_msg, level="WARNING") output_queue.put(("BUILD_ERROR", warn_msg)) # Treat as error if output dir is missing else: error_msg = f"Build failed! (Exit Code: {return_code})\nCheck the log for details." output_queue.put(("BUILD_ERROR", error_msg)) except FileNotFoundError: # This occurs if 'pyinstaller' command itself is not found error_msg = ("Error: 'pyinstaller' command not found.\n" "Ensure PyInstaller is installed correctly and in the system PATH.") output_queue.put(("BUILD_ERROR", error_msg)) logger_func(error_msg, level="CRITICAL") # Logged via logger_func as well except Exception as e: # Catch any other unexpected errors during process execution error_msg = f"Unexpected error during build process execution: {e}" output_queue.put(("BUILD_ERROR", error_msg)) logger_func(error_msg, level="CRITICAL") # Logged via logger_func logger_func(traceback.format_exc(), level="DEBUG") # Log full traceback for debugging finally: # Always signal that the build attempt (successful or not) has finished output_queue.put(("BUILD_FINISHED", None)) logger_func("Build thread finished.", level="INFO")