115 lines
6.0 KiB
Python
115 lines
6.0 KiB
Python
# -*- 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
|
|
|
|
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.
|
|
|
|
Designed to be run in a separate thread. Captures stdout/stderr and sends
|
|
messages (LOG, BUILD_SUCCESS, BUILD_ERROR, BUILD_FINISHED) back to the
|
|
main GUI thread via the provided queue.
|
|
|
|
Args:
|
|
command (list): The command list to execute (e.g., ['pyinstaller', ...]).
|
|
working_dir (str): The directory where the command should be executed.
|
|
output_queue (queue.Queue): The queue to send status messages back.
|
|
logger_func (callable): Function for logging (takes message, level).
|
|
output_dir_name (str): Expected output directory name (e.g., '_dist').
|
|
environment (dict, optional): Environment variables for the subprocess.
|
|
Defaults to None (inherit/use system default).
|
|
"""
|
|
build_process = None # Define variable outside try block for broader scope if needed
|
|
try:
|
|
logger_func("Build thread starting execution...", level="INFO")
|
|
|
|
# Log the environment being used if it's custom
|
|
env_log_msg = f"Using {'custom' if environment else 'inherited'} environment."
|
|
if environment:
|
|
# Optionally log specific variables for debugging (be careful with sensitive data)
|
|
# env_details = {k: v for k, v in environment.items() if 'TCL' in k or 'TK' in k or 'PATH' in k}
|
|
# env_log_msg += f" Details (partial): {env_details}"
|
|
pass # Avoid logging full env by default
|
|
logger_func(f"PyInstaller process starting. {env_log_msg}", level="INFO")
|
|
|
|
|
|
# Start the PyInstaller process
|
|
build_process = subprocess.Popen(
|
|
command,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT, # Redirect stderr to stdout
|
|
cwd=working_dir, # Set working directory
|
|
text=True, # Decode output as text
|
|
encoding='utf-8', # Specify encoding
|
|
errors='replace', # Handle decoding errors gracefully
|
|
bufsize=1, # Line-buffered output
|
|
env=environment # Pass the specific environment
|
|
# Note: creationflags might be needed on Windows to hide console,
|
|
# e.g., creationflags=subprocess.CREATE_NO_WINDOW
|
|
)
|
|
logger_func(f"PyInstaller process launched (PID: {build_process.pid}).", level="INFO")
|
|
|
|
# --- Read output line by line in real-time ---
|
|
while True:
|
|
try:
|
|
line = build_process.stdout.readline()
|
|
if not line:
|
|
# Check if process ended before breaking the loop
|
|
if build_process.poll() is not None:
|
|
logger_func("End of PyInstaller output stream.", level="DEBUG")
|
|
break
|
|
else:
|
|
# Process might still be running, briefly wait or continue loop
|
|
pass # readline should block, so usually no need to sleep
|
|
else:
|
|
# Send the captured line to the GUI log via the queue
|
|
output_queue.put(("LOG", line))
|
|
except Exception as read_err:
|
|
# Log errors during stream reading
|
|
logger_func(f"Error reading PyInstaller output stream: {read_err}\n{traceback.format_exc()}", level="ERROR")
|
|
# Decide whether to break or continue based on the error
|
|
break # Safer to break if reading fails
|
|
|
|
# --- Wait for process completion and get exit code ---
|
|
return_code = build_process.wait()
|
|
logger_func(f"PyInstaller process finished with exit code: {return_code}", level="INFO")
|
|
|
|
# --- Send final status message to the queue ---
|
|
if return_code == 0:
|
|
dist_path_abs = os.path.join(working_dir, output_dir_name)
|
|
if os.path.exists(dist_path_abs):
|
|
success_msg = f"Build completato con successo!\nDirectory Output: {dist_path_abs}"
|
|
output_queue.put(("BUILD_SUCCESS", success_msg))
|
|
else:
|
|
# Build reported success, but output dir is missing!
|
|
warn_msg = (f"Build terminato con codice 0, ma directory output non trovata:\n"
|
|
f"{dist_path_abs}\nControlla il log completo per eventuali problemi.")
|
|
logger_func(warn_msg, level="WARNING")
|
|
output_queue.put(("BUILD_ERROR", warn_msg)) # Report as error to user
|
|
else:
|
|
# Build failed
|
|
error_msg = f"Build fallito! (Codice Uscita: {return_code})\nControlla il log per dettagli."
|
|
output_queue.put(("BUILD_ERROR", error_msg))
|
|
|
|
except FileNotFoundError:
|
|
# Handle case where the pyinstaller command itself isn't found
|
|
error_msg = ("Errore: Comando 'pyinstaller' non trovato.\n"
|
|
"Assicurati che PyInstaller sia installato correttamente e nel PATH.")
|
|
output_queue.put(("BUILD_ERROR", error_msg))
|
|
logger_func(error_msg, level="CRITICAL")
|
|
except Exception as e:
|
|
# Handle any other unexpected exceptions during subprocess management
|
|
error_msg = f"Errore inatteso durante l'esecuzione del processo di build: {e}"
|
|
output_queue.put(("BUILD_ERROR", error_msg))
|
|
logger_func(error_msg, level="CRITICAL")
|
|
logger_func(traceback.format_exc(), level="DEBUG") # Log full traceback for debugging
|
|
finally:
|
|
# --- Crucial: Always signal that the build finished ---
|
|
# This allows the main thread to re-enable the build button etc.
|
|
output_queue.put(("BUILD_FINISHED", None)) # Send finish signal
|
|
logger_func("Build thread finished.", level="INFO") |