aggiunta la funzione
This commit is contained in:
parent
271a352540
commit
d4749d35ed
@ -19,6 +19,7 @@ from .project_selector import ProjectManager
|
|||||||
from .options_manager import OptionsManager
|
from .options_manager import OptionsManager
|
||||||
from .output_logger import OutputLogger
|
from .output_logger import OutputLogger
|
||||||
from .data_files_manager import DataFilesManager
|
from .data_files_manager import DataFilesManager
|
||||||
|
from .simple_script_builder import SimpleScriptBuilder
|
||||||
from pyinstallerguiwrapper.build.version_manager import VersionManager
|
from pyinstallerguiwrapper.build.version_manager import VersionManager
|
||||||
from pyinstallerguiwrapper.build.build_orchestrator import BuildOrchestrator
|
from pyinstallerguiwrapper.build.build_orchestrator import BuildOrchestrator
|
||||||
from pyinstallerguiwrapper import config
|
from pyinstallerguiwrapper import config
|
||||||
@ -57,6 +58,17 @@ class PyInstallerGUI(tk.Tk):
|
|||||||
self.rowconfigure(0, weight=1)
|
self.rowconfigure(0, weight=1)
|
||||||
self.columnconfigure(0, weight=1)
|
self.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# Create main mode notebook (tabs for different build modes)
|
||||||
|
self.mode_notebook = ttk.Notebook(self.main_frame)
|
||||||
|
|
||||||
|
# Create frames for each mode
|
||||||
|
self.project_mode_frame = ttk.Frame(self.mode_notebook, padding="10")
|
||||||
|
self.simple_mode_frame = ttk.Frame(self.mode_notebook, padding="10")
|
||||||
|
|
||||||
|
# Add tabs
|
||||||
|
self.mode_notebook.add(self.project_mode_frame, text="Project Mode")
|
||||||
|
self.mode_notebook.add(self.simple_mode_frame, text="Simple Script Mode")
|
||||||
|
|
||||||
self._create_widgets_frames_and_output_log_area()
|
self._create_widgets_frames_and_output_log_area()
|
||||||
|
|
||||||
self.output_logger = OutputLogger(
|
self.output_logger = OutputLogger(
|
||||||
@ -125,6 +137,11 @@ class PyInstallerGUI(tk.Tk):
|
|||||||
update_derived_spec_label_callback=self._update_derived_spec_label_gui
|
update_derived_spec_label_callback=self._update_derived_spec_label_gui
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.simple_script_builder = SimpleScriptBuilder(
|
||||||
|
parent_frame=self.simple_mode_frame,
|
||||||
|
logger_func=self.output_logger.log_message
|
||||||
|
)
|
||||||
|
|
||||||
self._layout_widgets()
|
self._layout_widgets()
|
||||||
|
|
||||||
self.after(100, self._check_main_gui_action_queue)
|
self.after(100, self._check_main_gui_action_queue)
|
||||||
@ -184,13 +201,13 @@ class PyInstallerGUI(tk.Tk):
|
|||||||
self.after(100, self._check_main_gui_action_queue)
|
self.after(100, self._check_main_gui_action_queue)
|
||||||
|
|
||||||
def _create_widgets_frames_and_output_log_area(self) -> None:
|
def _create_widgets_frames_and_output_log_area(self) -> None:
|
||||||
# Questo metodo non ha bisogno di modifiche, lo ometto per brevità
|
# === PROJECT MODE WIDGETS (in project_mode_frame) ===
|
||||||
self.project_frame = ttk.LabelFrame(self.main_frame, text="Project Directory")
|
self.project_frame = ttk.LabelFrame(self.project_mode_frame, text="Project Directory")
|
||||||
self.project_dir_label = ttk.Label(self.project_frame, text="Main Project Directory:")
|
self.project_dir_label = ttk.Label(self.project_frame, text="Main Project Directory:")
|
||||||
self.project_dir_entry = ttk.Entry(self.project_frame, textvariable=self.project_directory_path, state="readonly", width=80)
|
self.project_dir_entry = ttk.Entry(self.project_frame, textvariable=self.project_directory_path, state="readonly", width=80)
|
||||||
self.project_dir_button_browse = ttk.Button(self.project_frame, text="Browse...")
|
self.project_dir_button_browse = ttk.Button(self.project_frame, text="Browse...")
|
||||||
|
|
||||||
self.derived_paths_frame = ttk.LabelFrame(self.main_frame, text="Derived Paths (Automatic)")
|
self.derived_paths_frame = ttk.LabelFrame(self.project_mode_frame, text="Derived Paths (Automatic)")
|
||||||
self.derived_script_label_info = ttk.Label(self.derived_paths_frame, text="Script Found:")
|
self.derived_script_label_info = ttk.Label(self.derived_paths_frame, text="Script Found:")
|
||||||
self.derived_script_label_val = ttk.Label(self.derived_paths_frame, text="N/A", foreground="grey", anchor="w")
|
self.derived_script_label_val = ttk.Label(self.derived_paths_frame, text="N/A", foreground="grey", anchor="w")
|
||||||
self.derived_spec_label_info = ttk.Label(self.derived_paths_frame, text="Spec File Found:")
|
self.derived_spec_label_info = ttk.Label(self.derived_paths_frame, text="Spec File Found:")
|
||||||
@ -198,7 +215,7 @@ class PyInstallerGUI(tk.Tk):
|
|||||||
self.derived_icon_label_info = ttk.Label(self.derived_paths_frame, text="Icon Found:")
|
self.derived_icon_label_info = ttk.Label(self.derived_paths_frame, text="Icon Found:")
|
||||||
self.derived_icon_label_val = ttk.Label(self.derived_paths_frame, text="N/A", foreground="grey", anchor="w")
|
self.derived_icon_label_val = ttk.Label(self.derived_paths_frame, text="N/A", foreground="grey", anchor="w")
|
||||||
|
|
||||||
self.options_notebook = ttk.Notebook(self.main_frame)
|
self.options_notebook = ttk.Notebook(self.project_mode_frame)
|
||||||
self.basic_options_frame = ttk.Frame(self.options_notebook, padding="10")
|
self.basic_options_frame = ttk.Frame(self.options_notebook, padding="10")
|
||||||
self.options_notebook.add(self.basic_options_frame, text="Basic Options (from Spec/GUI)")
|
self.options_notebook.add(self.basic_options_frame, text="Basic Options (from Spec/GUI)")
|
||||||
|
|
||||||
@ -219,23 +236,29 @@ class PyInstallerGUI(tk.Tk):
|
|||||||
self.data_button_add_dir = ttk.Button(self.data_buttons_frame, text="Add Folder...")
|
self.data_button_add_dir = ttk.Button(self.data_buttons_frame, text="Add Folder...")
|
||||||
self.data_button_remove = ttk.Button(self.data_buttons_frame, text="Remove Selected")
|
self.data_button_remove = ttk.Button(self.data_buttons_frame, text="Remove Selected")
|
||||||
|
|
||||||
|
self.project_action_frame = ttk.Frame(self.project_mode_frame)
|
||||||
|
self.build_button = ttk.Button(self.project_action_frame, text="Build Executable", command=self._trigger_build_process, state="disabled")
|
||||||
|
|
||||||
|
# === SHARED OUTPUT LOG (in main_frame, outside tabs) ===
|
||||||
self.output_frame = ttk.LabelFrame(self.main_frame, text="Build Log")
|
self.output_frame = ttk.LabelFrame(self.main_frame, text="Build Log")
|
||||||
self.output_text_widget = tkinter.scrolledtext.ScrolledText(self.output_frame, wrap=tk.WORD, height=15, state="disabled")
|
self.output_text_widget = tkinter.scrolledtext.ScrolledText(self.output_frame, wrap=tk.WORD, height=15, state="disabled")
|
||||||
|
|
||||||
self.action_frame = ttk.Frame(self.main_frame)
|
|
||||||
self.build_button = ttk.Button(self.action_frame, text="Build Executable", command=self._trigger_build_process, state="disabled")
|
|
||||||
|
|
||||||
def _layout_widgets(self) -> None:
|
def _layout_widgets(self) -> None:
|
||||||
# Questo metodo non ha bisogno di modifiche, lo ometto per brevità
|
# === LAYOUT PROJECT MODE TAB ===
|
||||||
self.project_dir_button_browse.config(command=self.project_manager.select_project_directory)
|
self.project_dir_button_browse.config(command=self.project_manager.select_project_directory)
|
||||||
self.main_frame.rowconfigure(0, weight=0)
|
self.project_mode_frame.rowconfigure(0, weight=0)
|
||||||
|
self.project_mode_frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# Project frame
|
||||||
self.project_frame.grid(row=0, column=0, padx=5, pady=(0, 5), sticky="ew")
|
self.project_frame.grid(row=0, column=0, padx=5, pady=(0, 5), sticky="ew")
|
||||||
self.project_frame.columnconfigure(1, weight=1)
|
self.project_frame.columnconfigure(1, weight=1)
|
||||||
self.project_dir_label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
|
self.project_dir_label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
|
||||||
self.project_dir_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
|
self.project_dir_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
|
||||||
self.project_dir_button_browse.grid(row=0, column=2, padx=5, pady=5)
|
self.project_dir_button_browse.grid(row=0, column=2, padx=5, pady=5)
|
||||||
|
|
||||||
|
# Derived paths frame
|
||||||
row_idx = 1
|
row_idx = 1
|
||||||
self.main_frame.rowconfigure(row_idx, weight=0)
|
self.project_mode_frame.rowconfigure(row_idx, weight=0)
|
||||||
self.derived_paths_frame.grid(row=row_idx, column=0, padx=5, pady=5, sticky="ew")
|
self.derived_paths_frame.grid(row=row_idx, column=0, padx=5, pady=5, sticky="ew")
|
||||||
self.derived_paths_frame.columnconfigure(1, weight=1)
|
self.derived_paths_frame.columnconfigure(1, weight=1)
|
||||||
self.derived_script_label_info.grid(row=0, column=0, padx=5, pady=2, sticky="w")
|
self.derived_script_label_info.grid(row=0, column=0, padx=5, pady=2, sticky="w")
|
||||||
@ -244,9 +267,11 @@ class PyInstallerGUI(tk.Tk):
|
|||||||
self.derived_spec_label_val.grid(row=1, column=1, padx=5, pady=2, sticky="ew")
|
self.derived_spec_label_val.grid(row=1, column=1, padx=5, pady=2, sticky="ew")
|
||||||
self.derived_icon_label_info.grid(row=2, column=0, padx=5, pady=2, sticky="w")
|
self.derived_icon_label_info.grid(row=2, column=0, padx=5, pady=2, sticky="w")
|
||||||
self.derived_icon_label_val.grid(row=2, column=1, padx=5, pady=2, sticky="ew")
|
self.derived_icon_label_val.grid(row=2, column=1, padx=5, pady=2, sticky="ew")
|
||||||
|
|
||||||
|
# Options notebook
|
||||||
row_idx += 1
|
row_idx += 1
|
||||||
self.main_frame.rowconfigure(row_idx, weight=0)
|
self.project_mode_frame.rowconfigure(row_idx, weight=1)
|
||||||
self.options_notebook.grid(row=row_idx, column=0, padx=5, pady=5, sticky="ew")
|
self.options_notebook.grid(row=row_idx, column=0, padx=5, pady=5, sticky="nsew")
|
||||||
self.data_files_frame.columnconfigure(0, weight=1)
|
self.data_files_frame.columnconfigure(0, weight=1)
|
||||||
self.data_files_frame.rowconfigure(1, weight=1)
|
self.data_files_frame.rowconfigure(1, weight=1)
|
||||||
self.data_list_label.grid(row=0, column=0, columnspan=2, padx=5, pady=(0,5), sticky="w")
|
self.data_list_label.grid(row=0, column=0, columnspan=2, padx=5, pady=(0,5), sticky="w")
|
||||||
@ -260,16 +285,26 @@ class PyInstallerGUI(tk.Tk):
|
|||||||
self.data_button_add_file.config(command=self.data_files_manager.add_file)
|
self.data_button_add_file.config(command=self.data_files_manager.add_file)
|
||||||
self.data_button_add_dir.config(command=self.data_files_manager.add_directory)
|
self.data_button_add_dir.config(command=self.data_files_manager.add_directory)
|
||||||
self.data_button_remove.config(command=self.data_files_manager.remove_selected)
|
self.data_button_remove.config(command=self.data_files_manager.remove_selected)
|
||||||
|
|
||||||
|
# Action frame
|
||||||
row_idx += 1
|
row_idx += 1
|
||||||
self.main_frame.rowconfigure(row_idx, weight=1)
|
self.project_mode_frame.rowconfigure(row_idx, weight=0)
|
||||||
self.output_frame.grid(row=row_idx, column=0, padx=5, pady=5, sticky="nsew")
|
self.project_action_frame.grid(row=row_idx, column=0, padx=5, pady=(5,0), sticky="e")
|
||||||
|
self.build_button.pack(pady=5)
|
||||||
|
|
||||||
|
# === LAYOUT MAIN FRAME (MODE NOTEBOOK + OUTPUT LOG) ===
|
||||||
|
self.main_frame.rowconfigure(0, weight=1)
|
||||||
|
self.main_frame.rowconfigure(1, weight=1)
|
||||||
|
self.main_frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# Mode notebook (tabs)
|
||||||
|
self.mode_notebook.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
|
||||||
|
|
||||||
|
# Shared output log
|
||||||
|
self.output_frame.grid(row=1, column=0, padx=5, pady=5, sticky="nsew")
|
||||||
self.output_frame.rowconfigure(0, weight=1)
|
self.output_frame.rowconfigure(0, weight=1)
|
||||||
self.output_frame.columnconfigure(0, weight=1)
|
self.output_frame.columnconfigure(0, weight=1)
|
||||||
self.output_text_widget.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
|
self.output_text_widget.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
|
||||||
row_idx += 1
|
|
||||||
self.main_frame.rowconfigure(row_idx, weight=0)
|
|
||||||
self.action_frame.grid(row=row_idx, column=0, padx=5, pady=(5,0), sticky="e")
|
|
||||||
self.build_button.pack(pady=5)
|
|
||||||
|
|
||||||
def _on_project_selected(self, project_root_str: str, derived_paths_info: Dict[str, Any]) -> None:
|
def _on_project_selected(self, project_root_str: str, derived_paths_info: Dict[str, Any]) -> None:
|
||||||
# Questo metodo non ha bisogno di modifiche, lo ometto per brevità
|
# Questo metodo non ha bisogno di modifiche, lo ometto per brevità
|
||||||
|
|||||||
392
pyinstallerguiwrapper/gui/simple_script_builder.py
Normal file
392
pyinstallerguiwrapper/gui/simple_script_builder.py
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Simple Script Builder for PyInstaller GUI Wrapper.
|
||||||
|
Handles building executables from single Python scripts.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, filedialog, messagebox
|
||||||
|
import pathlib
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
from typing import Optional, Callable
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleScriptBuilder:
|
||||||
|
"""
|
||||||
|
Manages the simple script building interface and process.
|
||||||
|
Allows users to select a single Python script and build it into an executable.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent_frame: ttk.Frame,
|
||||||
|
logger_func: Optional[Callable[..., None]] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initializes the SimpleScriptBuilder.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent_frame: The parent Tkinter frame where this interface will be placed.
|
||||||
|
logger_func: A function to use for logging messages.
|
||||||
|
"""
|
||||||
|
self.parent_frame = parent_frame
|
||||||
|
self.logger = logger_func if logger_func else self._default_logger
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
self.script_path_var = tk.StringVar()
|
||||||
|
self.output_dir_var = tk.StringVar()
|
||||||
|
self.onefile_var = tk.BooleanVar(value=True)
|
||||||
|
self.is_building = False
|
||||||
|
|
||||||
|
self._create_widgets()
|
||||||
|
self._layout_widgets()
|
||||||
|
|
||||||
|
def _default_logger(self, message: str, level: str = "INFO") -> None:
|
||||||
|
"""Default logger if none is provided."""
|
||||||
|
print(f"[{level}] {message}")
|
||||||
|
|
||||||
|
def _log(self, message: str, level: str = "INFO") -> None:
|
||||||
|
"""Helper for logging messages with a consistent prefix."""
|
||||||
|
try:
|
||||||
|
self.logger(f"[SimpleScriptBuilder] {message}", level=level)
|
||||||
|
except TypeError:
|
||||||
|
self.logger(f"[{level}][SimpleScriptBuilder] {message}")
|
||||||
|
|
||||||
|
def _create_widgets(self) -> None:
|
||||||
|
"""Creates all widgets for the simple script builder interface."""
|
||||||
|
# Script selection frame
|
||||||
|
self.script_frame = ttk.LabelFrame(self.parent_frame, text="Python Script Selection", padding="10")
|
||||||
|
|
||||||
|
self.script_label = ttk.Label(self.script_frame, text="Select Python Script:")
|
||||||
|
self.script_entry = ttk.Entry(self.script_frame, textvariable=self.script_path_var, width=70)
|
||||||
|
self.script_browse_btn = ttk.Button(
|
||||||
|
self.script_frame,
|
||||||
|
text="Browse...",
|
||||||
|
command=self._browse_script
|
||||||
|
)
|
||||||
|
|
||||||
|
# Options frame
|
||||||
|
self.options_frame = ttk.LabelFrame(self.parent_frame, text="Build Options", padding="10")
|
||||||
|
|
||||||
|
self.onefile_check = ttk.Checkbutton(
|
||||||
|
self.options_frame,
|
||||||
|
text="Create single file executable (--onefile)",
|
||||||
|
variable=self.onefile_var
|
||||||
|
)
|
||||||
|
|
||||||
|
# Output directory frame
|
||||||
|
self.output_frame = ttk.LabelFrame(self.parent_frame, text="Output Directory", padding="10")
|
||||||
|
|
||||||
|
self.output_label = ttk.Label(self.output_frame, text="Output Directory:")
|
||||||
|
self.output_entry = ttk.Entry(self.output_frame, textvariable=self.output_dir_var, width=70)
|
||||||
|
self.output_browse_btn = ttk.Button(
|
||||||
|
self.output_frame,
|
||||||
|
text="Browse...",
|
||||||
|
command=self._browse_output_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
# Action buttons frame
|
||||||
|
self.action_frame = ttk.Frame(self.parent_frame, padding="10")
|
||||||
|
|
||||||
|
self.build_btn = ttk.Button(
|
||||||
|
self.action_frame,
|
||||||
|
text="Build Executable",
|
||||||
|
command=self._start_build,
|
||||||
|
state="disabled"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.open_folder_btn = ttk.Button(
|
||||||
|
self.action_frame,
|
||||||
|
text="Open Output Folder",
|
||||||
|
command=self._open_output_folder,
|
||||||
|
state="disabled"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _layout_widgets(self) -> None:
|
||||||
|
"""Layouts all widgets in the parent frame."""
|
||||||
|
# Script frame
|
||||||
|
self.script_frame.pack(fill=tk.X, padx=5, pady=5)
|
||||||
|
self.script_label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
|
||||||
|
self.script_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
|
||||||
|
self.script_browse_btn.grid(row=0, column=2, padx=5, pady=5)
|
||||||
|
self.script_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
# Options frame
|
||||||
|
self.options_frame.pack(fill=tk.X, padx=5, pady=5)
|
||||||
|
self.onefile_check.pack(anchor="w", padx=5, pady=5)
|
||||||
|
|
||||||
|
# Output frame
|
||||||
|
self.output_frame.pack(fill=tk.X, padx=5, pady=5)
|
||||||
|
self.output_label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
|
||||||
|
self.output_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
|
||||||
|
self.output_browse_btn.grid(row=0, column=2, padx=5, pady=5)
|
||||||
|
self.output_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
# Action buttons
|
||||||
|
self.action_frame.pack(fill=tk.X, padx=5, pady=10)
|
||||||
|
self.build_btn.pack(side=tk.LEFT, padx=5)
|
||||||
|
self.open_folder_btn.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
|
def _browse_script(self) -> None:
|
||||||
|
"""Opens a file dialog to select a Python script."""
|
||||||
|
file_path = filedialog.askopenfilename(
|
||||||
|
title="Select Python Script",
|
||||||
|
filetypes=[
|
||||||
|
("Python files", "*.py"),
|
||||||
|
("Python files (no console)", "*.pyw"),
|
||||||
|
("All files", "*.*")
|
||||||
|
],
|
||||||
|
parent=self.parent_frame
|
||||||
|
)
|
||||||
|
|
||||||
|
if file_path:
|
||||||
|
self.script_path_var.set(file_path)
|
||||||
|
self._log(f"Selected script: {file_path}", level="INFO")
|
||||||
|
|
||||||
|
# Auto-suggest output directory (same directory as script)
|
||||||
|
if not self.output_dir_var.get():
|
||||||
|
script_dir = str(pathlib.Path(file_path).parent)
|
||||||
|
self.output_dir_var.set(script_dir)
|
||||||
|
self._log(f"Auto-set output directory: {script_dir}", level="DEBUG")
|
||||||
|
|
||||||
|
self._update_build_button_state()
|
||||||
|
|
||||||
|
def _browse_output_dir(self) -> None:
|
||||||
|
"""Opens a dialog to select the output directory."""
|
||||||
|
dir_path = filedialog.askdirectory(
|
||||||
|
title="Select Output Directory",
|
||||||
|
parent=self.parent_frame
|
||||||
|
)
|
||||||
|
|
||||||
|
if dir_path:
|
||||||
|
self.output_dir_var.set(dir_path)
|
||||||
|
self._log(f"Selected output directory: {dir_path}", level="INFO")
|
||||||
|
self._update_build_button_state()
|
||||||
|
|
||||||
|
def _update_build_button_state(self) -> None:
|
||||||
|
"""Enables or disables the build button based on input validity."""
|
||||||
|
script_path = self.script_path_var.get()
|
||||||
|
output_dir = self.output_dir_var.get()
|
||||||
|
|
||||||
|
if script_path and pathlib.Path(script_path).is_file() and output_dir:
|
||||||
|
self.build_btn.config(state="normal")
|
||||||
|
else:
|
||||||
|
self.build_btn.config(state="disabled")
|
||||||
|
|
||||||
|
def _start_build(self) -> None:
|
||||||
|
"""Starts the build process in a separate thread."""
|
||||||
|
if self.is_building:
|
||||||
|
self._log("Build already in progress!", level="WARNING")
|
||||||
|
return
|
||||||
|
|
||||||
|
script_path = self.script_path_var.get()
|
||||||
|
output_dir = self.output_dir_var.get()
|
||||||
|
|
||||||
|
if not script_path or not pathlib.Path(script_path).is_file():
|
||||||
|
messagebox.showerror(
|
||||||
|
"Invalid Script",
|
||||||
|
"Please select a valid Python script file.",
|
||||||
|
parent=self.parent_frame
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not output_dir:
|
||||||
|
messagebox.showerror(
|
||||||
|
"Invalid Output Directory",
|
||||||
|
"Please select an output directory.",
|
||||||
|
parent=self.parent_frame
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.is_building = True
|
||||||
|
self.build_btn.config(state="disabled")
|
||||||
|
self._log("=" * 50, level="INFO")
|
||||||
|
self._log("STARTING SIMPLE SCRIPT BUILD", level="INFO")
|
||||||
|
self._log("=" * 50, level="INFO")
|
||||||
|
|
||||||
|
# Run build in a separate thread to avoid freezing the GUI
|
||||||
|
build_thread = threading.Thread(
|
||||||
|
target=self._run_build_process,
|
||||||
|
args=(script_path, output_dir),
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
build_thread.start()
|
||||||
|
|
||||||
|
def _run_build_process(self, script_path: str, output_dir: str) -> None:
|
||||||
|
"""
|
||||||
|
Runs the PyInstaller build process.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
script_path: Path to the Python script to build.
|
||||||
|
output_dir: Directory where build and dist folders will be created.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
script_path_obj = pathlib.Path(script_path)
|
||||||
|
output_dir_obj = pathlib.Path(output_dir)
|
||||||
|
|
||||||
|
# Create output directory if it doesn't exist
|
||||||
|
if not output_dir_obj.exists():
|
||||||
|
self._log(f"Creating output directory: {output_dir_obj}", level="INFO")
|
||||||
|
try:
|
||||||
|
output_dir_obj.mkdir(parents=True, exist_ok=True)
|
||||||
|
self._log(f"Output directory created successfully", level="INFO")
|
||||||
|
except Exception as dir_err:
|
||||||
|
self._log(f"Failed to create output directory: {dir_err}", level="ERROR")
|
||||||
|
error_msg = f"Cannot create output directory:\n{output_dir_obj}\n\nError: {dir_err}"
|
||||||
|
self.parent_frame.after(
|
||||||
|
0,
|
||||||
|
lambda err=error_msg: messagebox.showerror(
|
||||||
|
"Directory Creation Failed",
|
||||||
|
err,
|
||||||
|
parent=self.parent_frame
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
elif not output_dir_obj.is_dir():
|
||||||
|
error_msg = f"The specified path exists but is not a directory:\n{output_dir_obj}"
|
||||||
|
self._log(error_msg, level="ERROR")
|
||||||
|
self.parent_frame.after(
|
||||||
|
0,
|
||||||
|
lambda err=error_msg: messagebox.showerror(
|
||||||
|
"Invalid Path",
|
||||||
|
err,
|
||||||
|
parent=self.parent_frame
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Prepare PyInstaller command
|
||||||
|
cmd = [sys.executable, "-m", "PyInstaller"]
|
||||||
|
|
||||||
|
# Add onefile option if selected
|
||||||
|
if self.onefile_var.get():
|
||||||
|
cmd.append("--onefile")
|
||||||
|
self._log("Building as single file executable", level="INFO")
|
||||||
|
else:
|
||||||
|
self._log("Building as directory bundle", level="INFO")
|
||||||
|
|
||||||
|
# Set output directories
|
||||||
|
build_dir = output_dir_obj / "_build"
|
||||||
|
dist_dir = output_dir_obj / "_dist"
|
||||||
|
|
||||||
|
cmd.extend([
|
||||||
|
"--distpath", str(dist_dir),
|
||||||
|
"--workpath", str(build_dir),
|
||||||
|
"--specpath", str(output_dir_obj),
|
||||||
|
])
|
||||||
|
|
||||||
|
# Add the script path
|
||||||
|
cmd.append(str(script_path_obj))
|
||||||
|
|
||||||
|
self._log(f"Executing: {' '.join(cmd)}", level="DEBUG")
|
||||||
|
self._log(f"Working directory: {output_dir_obj}", level="DEBUG")
|
||||||
|
|
||||||
|
# Run PyInstaller
|
||||||
|
process = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
cwd=str(output_dir_obj),
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
text=True,
|
||||||
|
bufsize=1
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stream output to logger
|
||||||
|
if process.stdout:
|
||||||
|
for line in process.stdout:
|
||||||
|
line = line.rstrip()
|
||||||
|
if line:
|
||||||
|
self._log(line, level="INFO")
|
||||||
|
|
||||||
|
process.wait()
|
||||||
|
|
||||||
|
# Check result
|
||||||
|
if process.returncode == 0:
|
||||||
|
self._log("=" * 50, level="INFO")
|
||||||
|
self._log("BUILD COMPLETED SUCCESSFULLY!", level="INFO")
|
||||||
|
self._log(f"Output location: {dist_dir}", level="INFO")
|
||||||
|
self._log("=" * 50, level="INFO")
|
||||||
|
|
||||||
|
# Enable open folder button
|
||||||
|
self.open_folder_btn.config(state="normal")
|
||||||
|
|
||||||
|
# Show success message in main thread
|
||||||
|
self.parent_frame.after(
|
||||||
|
0,
|
||||||
|
lambda d=str(dist_dir): messagebox.showinfo(
|
||||||
|
"Build Successful",
|
||||||
|
f"Executable built successfully!\n\nOutput: {d}",
|
||||||
|
parent=self.parent_frame
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._log("=" * 50, level="ERROR")
|
||||||
|
self._log(f"BUILD FAILED! Exit code: {process.returncode}", level="ERROR")
|
||||||
|
self._log("=" * 50, level="ERROR")
|
||||||
|
|
||||||
|
# Show error message in main thread
|
||||||
|
self.parent_frame.after(
|
||||||
|
0,
|
||||||
|
lambda rc=process.returncode: messagebox.showerror(
|
||||||
|
"Build Failed",
|
||||||
|
f"Build process failed with exit code {rc}.\n\nCheck the log for details.",
|
||||||
|
parent=self.parent_frame
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._log(f"Error during build: {e}", level="ERROR")
|
||||||
|
import traceback
|
||||||
|
self._log(traceback.format_exc(), level="ERROR")
|
||||||
|
|
||||||
|
# Show error message in main thread
|
||||||
|
error_msg = str(e)
|
||||||
|
self.parent_frame.after(
|
||||||
|
0,
|
||||||
|
lambda err=error_msg: messagebox.showerror(
|
||||||
|
"Build Error",
|
||||||
|
f"An error occurred during the build:\n{err}",
|
||||||
|
parent=self.parent_frame
|
||||||
|
)
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
self.is_building = False
|
||||||
|
# Re-enable build button in main thread
|
||||||
|
self.parent_frame.after(0, self._update_build_button_state)
|
||||||
|
|
||||||
|
def _open_output_folder(self) -> None:
|
||||||
|
"""Opens the output folder in the system file explorer."""
|
||||||
|
output_dir = self.output_dir_var.get()
|
||||||
|
|
||||||
|
if not output_dir or not pathlib.Path(output_dir).is_dir():
|
||||||
|
messagebox.showerror(
|
||||||
|
"Invalid Directory",
|
||||||
|
"Output directory does not exist.",
|
||||||
|
parent=self.parent_frame
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
dist_dir = pathlib.Path(output_dir) / "_dist"
|
||||||
|
|
||||||
|
# Use the dist directory if it exists, otherwise use the output directory
|
||||||
|
folder_to_open = dist_dir if dist_dir.is_dir() else pathlib.Path(output_dir)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if sys.platform == "win32":
|
||||||
|
os.startfile(str(folder_to_open))
|
||||||
|
elif sys.platform == "darwin":
|
||||||
|
subprocess.run(["open", str(folder_to_open)])
|
||||||
|
else:
|
||||||
|
subprocess.run(["xdg-open", str(folder_to_open)])
|
||||||
|
|
||||||
|
self._log(f"Opened folder: {folder_to_open}", level="INFO")
|
||||||
|
except Exception as e:
|
||||||
|
self._log(f"Failed to open folder: {e}", level="ERROR")
|
||||||
|
messagebox.showerror(
|
||||||
|
"Error",
|
||||||
|
f"Failed to open folder:\n{e}",
|
||||||
|
parent=self.parent_frame
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue
Block a user