From 5dcfa797e9a5a121a21476ce0d446793d7f9d4a6 Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Mon, 15 Dec 2025 10:37:05 +0100 Subject: [PATCH] aggiornato lettore per gestire pacchetti come pygount --- .../build/build_orchestrator.py | 69 +++++++++++++++++-- .../gui/spec_editor_panel.py | 10 +++ pyinstallerguiwrapper/spec_parser.py | 3 + pyucc.spec | 23 +++++++ 4 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 pyucc.spec diff --git a/pyinstallerguiwrapper/build/build_orchestrator.py b/pyinstallerguiwrapper/build/build_orchestrator.py index 43a137b..aa95e81 100644 --- a/pyinstallerguiwrapper/build/build_orchestrator.py +++ b/pyinstallerguiwrapper/build/build_orchestrator.py @@ -477,6 +477,9 @@ exe = EXE( parsed_spec_opts = self._get_last_parsed_spec_options() or {} final_hiddenimports = list(parsed_spec_opts.get('hiddenimports', []) or []) + # Preserve hookspath and runtime_hooks from existing spec if present + final_hookspath = parsed_spec_opts.get('hookspath', []) or [] + final_runtime_hooks = parsed_spec_opts.get('runtime_hooks', []) or [] # Use a user-confirmed list if provided by the GUI; otherwise, optionally # run the static detector when the option is enabled. try: @@ -500,6 +503,22 @@ exe = EXE( self._log_message(f"Dependency detection failed: {e}", level="WARNING") final_binaries = parsed_spec_opts.get('binaries', []) + # Detect packages that were packaged as wheels/datas which may need collect_all + collect_all_pkgs: List[str] = [] + try: + datas_from_spec = parsed_spec_opts.get('datas', []) or [] + for d in datas_from_spec: + # datas entries may be tuples like (src, dest) + try: + src = d[0] if isinstance(d, (list, tuple)) and len(d) > 0 else str(d) + except Exception: + src = str(d) + src_lower = str(src).lower() + if 'pygount' in src_lower and 'pygount' not in collect_all_pkgs: + collect_all_pkgs.append('pygount') + except Exception: + pass + analysis_pathex_list = [] if source_dir_rel_path and source_dir_rel_path != ".": analysis_pathex_list.append(source_dir_rel_path) @@ -511,6 +530,9 @@ exe = EXE( 'formatted_datas': current_datas_for_spec, 'formatted_hiddenimports': final_hiddenimports, 'formatted_binaries': final_binaries, + 'formatted_hookspath': final_hookspath, + 'formatted_runtime_hooks': final_runtime_hooks, + 'collect_all_pkgs': collect_all_pkgs, 'app_name': self._get_app_name(), 'icon_rel_path': icon_rel_path_for_spec, 'is_windowed': self._get_is_windowed(), @@ -574,10 +596,35 @@ exe = EXE( cipher_var, analysis_var, pyz_var = gui_options_dict['pyz_cipher_var_name'], gui_options_dict['a_var_name'], gui_options_dict['pyz_var_name'] - nodes: List[ast.stmt] = [ - ast.Expr(value=create_node("\n# PyInstaller spec file generated by GUI Wrapper.\n")), - ast.Assign(targets=[ast.Name(id=cipher_var, ctx=ast.Store())], value=create_node(None)), - ast.Assign( + nodes: List[ast.stmt] = [] + + # Optional: support injecting collect_all(...) assignments for packages + collect_all_pkgs = gui_options_dict.get('collect_all_pkgs', []) or [] + if collect_all_pkgs: + # Add: from PyInstaller.utils.hooks import collect_all + try: + import_node = ast.ImportFrom(module='PyInstaller.utils.hooks', names=[ast.alias(name='collect_all', asname=None)], level=0) + nodes.append(import_node) + except Exception: + pass + for pkg in collect_all_pkgs: + # Create names like _pygount_datas, _pygount_binaries, _pygount_hiddenimports + safe_pkg = pkg.replace('-', '_') + names_tuple = ast.Tuple(elts=[ + ast.Name(id=f'_{safe_pkg}_datas', ctx=ast.Store()), + ast.Name(id=f'_{safe_pkg}_binaries', ctx=ast.Store()), + ast.Name(id=f'_{safe_pkg}_hiddenimports', ctx=ast.Store()) + ], ctx=ast.Store()) + call_node = ast.Call(func=ast.Name(id='collect_all', ctx=ast.Load()), args=[ast.Constant(value=pkg)], keywords=[]) + assign_node = ast.Assign(targets=[names_tuple], value=call_node) + nodes.append(assign_node) + + # Standard leading comment and cipher assignment + nodes.append(ast.Expr(value=create_node("\n# PyInstaller spec file generated by GUI Wrapper.\n"))) + nodes.append(ast.Assign(targets=[ast.Name(id=cipher_var, ctx=ast.Store())], value=create_node(None))) + + # Build Analysis node + analysis_node = ast.Assign( targets=[ast.Name(id=analysis_var, ctx=ast.Store())], value=ast.Call( func=ast.Name(id='Analysis', ctx=ast.Load()), @@ -589,16 +636,24 @@ exe = EXE( ast.keyword(arg='binaries', value=create_node(gui_options_dict.get('formatted_binaries',[]))), ast.keyword(arg='datas', value=create_node(gui_options_dict.get('formatted_datas', []))), ast.keyword(arg='hiddenimports', value=create_node(gui_options_dict.get('formatted_hiddenimports',[]))), - ast.keyword(arg='hookspath', value=create_node([])), + ast.keyword(arg='hookspath', value=create_node(gui_options_dict.get('formatted_hookspath', []))), ast.keyword(arg='hooksconfig', value=ast.Dict(keys=[], values=[])), - ast.keyword(arg='runtime_hooks', value=create_node([])), + ast.keyword(arg='runtime_hooks', value=create_node(gui_options_dict.get('formatted_runtime_hooks', []))), ast.keyword(arg='excludes', value=create_node([])), ast.keyword(arg='win_no_prefer_redirects', value=create_node(False)), ast.keyword(arg='win_private_assemblies', value=create_node(False)), ast.keyword(arg='cipher', value=ast.Name(id=cipher_var, ctx=ast.Load())), ast.keyword(arg='noarchive', value=create_node(False)) ])) - ] + nodes.append(analysis_node) + + # If we injected collect_all assignments, append try/except blocks to add hiddenimports to 'a' + if collect_all_pkgs: + for pkg in collect_all_pkgs: + safe_pkg = pkg.replace('-', '_') + aug = ast.AugAssign(target=ast.Attribute(value=ast.Name(id='a', ctx=ast.Load()), attr='hiddenimports', ctx=ast.Store()), op=ast.Add(), value=ast.Name(id=f'_{safe_pkg}_hiddenimports', ctx=ast.Load())) + try_node = ast.Try(body=[aug], handlers=[ast.ExceptHandler(type=None, name=None, body=[ast.Pass()])], orelse=[], finalbody=[]) + nodes.append(try_node) nodes.append(ast.Assign( targets=[ast.Name(id=pyz_var, ctx=ast.Store())], value=ast.Call( diff --git a/pyinstallerguiwrapper/gui/spec_editor_panel.py b/pyinstallerguiwrapper/gui/spec_editor_panel.py index 5650df1..8bd1bfb 100644 --- a/pyinstallerguiwrapper/gui/spec_editor_panel.py +++ b/pyinstallerguiwrapper/gui/spec_editor_panel.py @@ -58,6 +58,16 @@ class SpecEditorPanel(ttk.Frame): # Do an initial silent refresh (do not show messageboxes when no spec) self.refresh_calls(silent=True) + # Bind combobox selection to auto-refresh keywords list (no need to click Refresh) + try: + self.call_select.bind('<>', lambda e: self.refresh_calls()) + except Exception: + pass + # Bind listbox selection to auto-load keyword when user selects an entry + try: + self.keywords_listbox.bind('<>', lambda e: self.load_selected_keyword()) + except Exception: + pass def _log(self, msg: str, level: str = 'INFO'): try: diff --git a/pyinstallerguiwrapper/spec_parser.py b/pyinstallerguiwrapper/spec_parser.py index 0b7e7b8..a4f49e3 100644 --- a/pyinstallerguiwrapper/spec_parser.py +++ b/pyinstallerguiwrapper/spec_parser.py @@ -141,6 +141,9 @@ class _SpecVisitor(ast.NodeVisitor): self.parsed_options["pathex"] = positional_args[1] or [] self.parsed_options["datas"] = options.get("datas", []) self.parsed_options["hiddenimports"] = options.get("hiddenimports", []) + # Capture optional hookspath and runtime_hooks if present in Analysis + self.parsed_options["hookspath"] = options.get("hookspath", []) + self.parsed_options["runtime_hooks"] = options.get("runtime_hooks", []) self.parsed_options["binaries"] = options.get("binaries", []) elif call_name == "EXE": self._log(f"Found EXE() call.") diff --git a/pyucc.spec b/pyucc.spec new file mode 100644 index 0000000..d722821 --- /dev/null +++ b/pyucc.spec @@ -0,0 +1,23 @@ +from PyInstaller.utils.hooks import collect_all + +# Ensure pygount package contents are explicitly collected (datas, binaries, hiddenimports) +_pygount_datas, _pygount_binaries, _pygount_hiddenimports = collect_all('pygount') + +block_cipher = None +a = Analysis(pathex=['pyucc', '.'], binaries=[], datas=[('pyucc/_internal/pygount-3.0.0-py3-none-any.whl', '_internal'),('PyUcc.ico', '.'), ('external\\python-tkinter-logger\\tkinter_logger.py', '.'), ('external\\python-resource-monitor\\resource_monitor.py', '.'), ('external\\python-resource-monitor', 'python-resource-monitor'), ('external\\python-tkinter-logger', 'python-tkinter-logger'), ('external\\_setup_paths.py', '.'), ('external\\python-resource-monitor', 'python-resource-monitor'), ('external\\python-tkinter-logger', 'python-tkinter-logger'), ('external\\_setup_paths.py', '.'), ('external\\python-resource-monitor', 'python-resource-monitor'), ('external\\python-tkinter-logger', 'python-tkinter-logger'), ('external\\_setup_paths.py', '.'), ('external\\python-resource-monitor', 'python-resource-monitor'), ('external\\python-tkinter-logger', 'python-tkinter-logger'), ('wheels\\pygount-3.1.0-py3-none-any.whl', 'pyucc\\_internal')], hiddenimports=['tkinter_logger', 'resource_monitor', 'pyucc.core.differ', 'pyucc.gui.gui', 'pyucc.config.settings', 'logging', 'logging.handlers', 'logging.config', 'ConfigParser', 'Cython', 'HTMLParser', 'IPython', 'OpenSSL', 'PIL', 'Queue', 'StringIO', 'android', 'annotationlib', 'argcomplete', 'attr', 'autocommand', 'backports', 'cgi', 'chardet', 'colorama', 'contextlib2', 'cryptography', 'ctags', 'distutils', 'dl', 'docutils', 'dummy_thread', 'dummy_threading', 'exceptiongroup', 'fcntl', 'filelock', 'future_builtins', 'git', 'gitdb', 'gitdb_speedups', 'google', 'grp', 'htmlentitydefs', 'httplib', 'importlib_metadata', 'importlib_resources', 'inflect', 'ini2toml', 'iniconfig', 'ipywidgets', 'jaraco', 'java', 'jinja2', 'jnius', 'keyring', 'linkify_it', 'lizard', 'lizard_ext', 'lizard_languages', 'markdown_it', 'mdurl', 'mock', 'mod', 'mod2', 'more_itertools', 'nspkg', 'ntlm', 'numpy', 'packaging', 'path', 'pathspec', 'pexpect', 'pip', 'pkg1', 'pkg_resources', 'platformdirs', 'pluggy', 'psutil', 'pwd', 'py', 'pygments', 'pygount', 'pytest', 'pyucc', 'pywintypes', 'readline', 'redis', 'resource', 'rich', 'setuptools', 'sha', 'smmap', 'socks', 'sphinx', 'target_simulator', 'thread', 'tomli', 'tomli_w', 'trove_classifiers', 'twisted', 'typeguard', 'typeshed', 'typing_extensions', 'urllib2', 'urllib3_secure_extra', 'urlparse', 'wheel', 'win32api', 'win32con', 'win32process', 'wmi', 'xmlrpclib', 'xx', 'zipp', 'zope', 'traitlets', 'gdb', 'matplotlib', 'PyInstaller'], hookspath=['hooks'], runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False, scripts=['pyucc\\__main__.py']) +try: + a.hiddenimports += _pygount_hiddenimports +except Exception: + pass +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) +exe = EXE(pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name='PyUcc', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, upx_exclude=[], runtime_tmpdir=None, console=False, icon='PyUcc.ico', exclude_binaries=True) +coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name='PyUcc') +block_cipher = None +a = Analysis(pathex=['pyucc', '.'], binaries=[], datas=[('pyucc/_internal/pygount-3.0.0-py3-none-any.whl', '_internal'),('PyUcc.ico', '.'), ('external\\python-tkinter-logger\\tkinter_logger.py', '.'), ('external\\python-resource-monitor\\resource_monitor.py', '.'), ('external\\python-resource-monitor', 'python-resource-monitor'), ('external\\python-tkinter-logger', 'python-tkinter-logger'), ('external\\_setup_paths.py', '.'), ('external\\python-resource-monitor', 'python-resource-monitor'), ('external\\python-tkinter-logger', 'python-tkinter-logger'), ('external\\_setup_paths.py', '.'), ('external\\python-resource-monitor', 'python-resource-monitor'), ('external\\python-tkinter-logger', 'python-tkinter-logger'), ('external\\_setup_paths.py', '.'), ('external\\python-resource-monitor', 'python-resource-monitor'), ('external\\python-tkinter-logger', 'python-tkinter-logger')], hiddenimports=['tkinter_logger', 'resource_monitor', 'pyucc.core.differ', 'pyucc.gui.gui', 'pyucc.config.settings', 'logging', 'logging.handlers', 'logging.config', 'ConfigParser', 'Cython', 'HTMLParser', 'IPython', 'OpenSSL', 'PIL', 'Queue', 'StringIO', 'android', 'annotationlib', 'argcomplete', 'attr', 'autocommand', 'backports', 'cgi', 'chardet', 'colorama', 'contextlib2', 'cryptography', 'ctags', 'distutils', 'dl', 'docutils', 'dummy_thread', 'dummy_threading', 'exceptiongroup', 'fcntl', 'filelock', 'future_builtins', 'git', 'gitdb', 'gitdb_speedups', 'google', 'grp', 'htmlentitydefs', 'httplib', 'importlib_metadata', 'importlib_resources', 'inflect', 'ini2toml', 'iniconfig', 'ipywidgets', 'jaraco', 'java', 'jinja2', 'jnius', 'keyring', 'linkify_it', 'lizard', 'lizard_ext', 'lizard_languages', 'markdown_it', 'mdurl', 'mock', 'mod', 'mod2', 'more_itertools', 'nspkg', 'ntlm', 'numpy', 'packaging', 'path', 'pathspec', 'pexpect', 'pip', 'pkg1', 'pkg_resources', 'platformdirs', 'pluggy', 'psutil', 'pwd', 'py', 'pygments', 'pygount', 'pytest', 'pyucc', 'pywintypes', 'readline', 'redis', 'resource', 'rich', 'setuptools', 'sha', 'smmap', 'socks', 'sphinx', 'target_simulator', 'thread', 'tomli', 'tomli_w', 'trove_classifiers', 'twisted', 'typeguard', 'typeshed', 'typing_extensions', 'urllib2', 'urllib3_secure_extra', 'urlparse', 'wheel', 'win32api', 'win32con', 'win32process', 'wmi', 'xmlrpclib', 'xx', 'zipp', 'zope', 'traitlets', 'gdb', 'matplotlib', 'PyInstaller'], hookspath=[], runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False, scripts=['pyucc\\__main__.py']) +try: + a.hiddenimports += _pygount_hiddenimports +except Exception: + pass +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) +exe = EXE(pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name='PyUcc', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, upx_exclude=[], runtime_tmpdir=None, console=False, icon='PyUcc.ico', exclude_binaries=True) +coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name='PyUcc')