From 3a675da9594204c10458684680c989c4e1efe8de Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Mon, 15 Dec 2025 10:38:08 +0100 Subject: [PATCH] aggiunta gestione pacchetto pygount da installare --- hooks/hook-pygount.py | 7 + pyucc.spec.bak | 23 +++ pyucc/__main__.py | 8 +- .../_internal/pygount-3.0.0-py3-none-any.whl | Bin 0 -> 30176 bytes pyucc/_version.py | 6 +- pyucc/core/countings_impl.py | 76 ++++++++ pyucc/gui/gui.py | 18 +- settings.json | 10 +- tools/bundle_pygount.py | 162 ++++++++++++++++++ tools/check_pygount_build.py | 9 + 10 files changed, 301 insertions(+), 18 deletions(-) create mode 100644 hooks/hook-pygount.py create mode 100644 pyucc.spec.bak create mode 100644 pyucc/_internal/pygount-3.0.0-py3-none-any.whl create mode 100644 tools/bundle_pygount.py create mode 100644 tools/check_pygount_build.py diff --git a/hooks/hook-pygount.py b/hooks/hook-pygount.py new file mode 100644 index 0000000..ec70b9d --- /dev/null +++ b/hooks/hook-pygount.py @@ -0,0 +1,7 @@ +from PyInstaller.utils.hooks import collect_all + +# Collect all package data, binaries and hidden imports from pygount +datas, binaries, hiddenimports = collect_all('pygount') + +# Export to PyInstaller +__all__ = ['datas', 'binaries', 'hiddenimports'] diff --git a/pyucc.spec.bak b/pyucc.spec.bak new file mode 100644 index 0000000..adec851 --- /dev/null +++ b/pyucc.spec.bak @@ -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.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=['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.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') diff --git a/pyucc/__main__.py b/pyucc/__main__.py index 7c14aed..0598240 100644 --- a/pyucc/__main__.py +++ b/pyucc/__main__.py @@ -151,19 +151,19 @@ def main(): if not getattr(countings_impl, "_HAS_PYGOUNT", False): print( - "ATTENZIONE: il pacchetto 'pygount' non è disponibile. I conteggi dei commenti e le metriche estese potrebbero non essere disponibili.", + "WARNING: the 'pygount' package is not available. Comment counts and some extended metrics may not be available.", file=sys.stderr, ) print( - "Per risolvere: attiva la virtualenv usata per eseguire l'app e esegui 'pip install pygount'", + "To fix: activate the virtualenv used to run the app and run 'pip install pygount'", file=sys.stderr, ) print( - "Se distribuisci con PyInstaller, ricostruisci l'eseguibile includendo 'pygount' (aggiungi un hook o hiddenimports).", + "If you ship a PyInstaller executable, rebuild it including 'pygount' (add a hook or hiddenimports).", file=sys.stderr, ) except Exception: - # Non critico: continuiamo senza interrompere l'esecuzione + # Non-critical: continue execution pass # If user asked for GUI, or no positional directories were provided, launch GUI and exit if args.gui or len(args.baseline_dirs or []) == 0: diff --git a/pyucc/_internal/pygount-3.0.0-py3-none-any.whl b/pyucc/_internal/pygount-3.0.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..b796969f55c032fd1743eb1b903bda54e700f78c GIT binary patch literal 30176 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 literal 0 HcmV?d00001 diff --git a/pyucc/_version.py b/pyucc/_version.py index fb6f085..eb8d0ed 100644 --- a/pyucc/_version.py +++ b/pyucc/_version.py @@ -6,10 +6,10 @@ import re # --- Version Data (Generated) --- -__version__ = "v.0.0.0.22-0-gea1d31b-dirty" -GIT_COMMIT_HASH = "ea1d31b82e9fa64a3bb809c001f8c465fbcb0294" +__version__ = "v.0.0.0.23-0-gc67280d-dirty" +GIT_COMMIT_HASH = "c67280df44cb0db1bad3815a2938adc0904a10de" GIT_BRANCH = "master" -BUILD_TIMESTAMP = "2025-12-15T07:30:39.921932+00:00" +BUILD_TIMESTAMP = "2025-12-15T09:32:07.391542+00:00" IS_GIT_REPO = True # --- Default Values (for comparison or fallback) --- diff --git a/pyucc/core/countings_impl.py b/pyucc/core/countings_impl.py index 76ea415..b57c076 100644 --- a/pyucc/core/countings_impl.py +++ b/pyucc/core/countings_impl.py @@ -10,14 +10,90 @@ import subprocess import logging import threading import hashlib +import os +import sys +import glob try: import pygount # type: ignore _HAS_PYGOUNT = True except Exception: + # Try to load a bundled pygount wheel (offline install) — useful for PyInstaller one-folder _HAS_PYGOUNT = False + def _attempt_load_bundled_pygount() -> bool: + """If a pygount wheel is bundled with the app (e.g. in _internal), add it to sys.path and try import. + + Returns True if import succeeded. + """ + + candidates = [] + # If running frozen (PyInstaller), packaged data are extracted to sys._MEIPASS + base = getattr(sys, "_MEIPASS", None) + if base: + # search both at root of MEIPASS and recursively (some datas end up nested) + candidates.append(os.path.join(base, "pygount*.whl")) + candidates.append(os.path.join(base, "**", "pygount*.whl")) + candidates.append(os.path.join(base, "pygount", "**", "*.py")) + candidates.append(os.path.join(base, "**", "pygount", "**", "*.py")) + # Also look into a relative _internal folder in source tree + repo_internal = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "_internal")) + candidates.append(os.path.join(repo_internal, "pygount*.whl")) + candidates.append(os.path.join(repo_internal, "pygount", "**", "*.py")) + + found_paths = [] + for pattern in candidates: + try: + for p in glob.glob(pattern, recursive=True): + if p and p not in found_paths: + found_paths.append(p) + except Exception: + continue + + # Prefer a wheel file if present (zipimport works for wheels) + wheel_paths = [p for p in found_paths if p.lower().endswith('.whl')] + if wheel_paths: + wheel = wheel_paths[0] + try: + if wheel not in sys.path: + sys.path.insert(0, wheel) + import pygount # type: ignore + + return True + except Exception: + # remove added path if import failed + try: + if wheel in sys.path: + sys.path.remove(wheel) + except Exception: + pass + + # If no wheel, try adding a plain package folder + pkg_dirs = [os.path.dirname(p) for p in found_paths if p.lower().endswith('.py')] + # remove duplicates + pkg_dirs = list(dict.fromkeys(pkg_dirs)) + for d in pkg_dirs: + try: + if d not in sys.path: + sys.path.insert(0, d) + import pygount # type: ignore + return True + except Exception: + try: + if d in sys.path: + sys.path.remove(d) + except Exception: + pass + + return False + + try: + if _attempt_load_bundled_pygount(): + _HAS_PYGOUNT = True + except Exception: + _HAS_PYGOUNT = False + _LOG = logging.getLogger(__name__) # Cache to store counting results by file content hash ONLY diff --git a/pyucc/gui/gui.py b/pyucc/gui/gui.py index a0a7b24..ac3e422 100644 --- a/pyucc/gui/gui.py +++ b/pyucc/gui/gui.py @@ -238,25 +238,25 @@ class App(tk.Tk): if not getattr(countings_impl, "_HAS_PYGOUNT", False): msg = ( """ -Il pacchetto 'pygount' non è disponibile. +The 'pygount' package is not available. -I conteggi dei commenti e alcune metriche estese potrebbero non funzionare correttamente. +Comment counting and some extended metrics may not work correctly. -Soluzioni possibili: - 1) Se usi l'ambiente di sviluppo: attiva la virtualenv e esegui 'pip install pygount'. - 2) Se usi l'eseguibile PyInstaller: ricostruiscilo includendo 'pygount' (hook o hiddenimports). - 3) Per controllare rapidamente: esegui - python -c 'from pyucc.core import countings_impl; print(countings_impl._HAS_PYGOUNT)' +Possible fixes: + 1) If you are using a development environment: activate the virtualenv and run 'pip install pygount'. + 2) If you are using a PyInstaller-built executable: rebuild it including 'pygount' (hook or hiddenimports). + 3) Quick check: run + python -c "from pyucc.core import countings_impl; print(countings_impl._HAS_PYGOUNT)" """ ) try: - messagebox.showwarning("Dipendenza mancante: pygount", msg) + messagebox.showwarning("Missing dependency: pygount", msg) except Exception: # If messagebox fails (headless), fallback to logging pass try: self.log( - "pygount non disponibile: conteggio commenti e metriche estese disabilitate", + "pygount not available: comment counting and extended metrics disabled", level="WARNING", ) except Exception: diff --git a/settings.json b/settings.json index bd40b8f..f84150e 100644 --- a/settings.json +++ b/settings.json @@ -5,8 +5,14 @@ "duplicates": { "threshold": 5.0, "extensions": [ - ".py", - ".pyw" + ".inl", + ".h", + ".hh", + ".c", + ".cxx", + ".hpp", + ".cpp", + ".cc" ], "k": 25, "window": 4 diff --git a/tools/bundle_pygount.py b/tools/bundle_pygount.py new file mode 100644 index 0000000..00ada66 --- /dev/null +++ b/tools/bundle_pygount.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +"""Bundle pygount wheel into the project and update pyucc.spec. + +Usage: run inside your project's virtualenv/checkout root: + python tools/bundle_pygount.py + +What it does: + - Locates a local `pygount-*.whl` in the current Python environment (site-packages) + - Copies it to `pyucc/_internal/` + - Updates `pyucc.spec`, adding the wheel as a `datas` entry so PyInstaller bundles it + +This enables the runtime fallback added in `pyucc.core.countings_impl` to import pygount +from a bundled wheel when running on an offline target. +""" +from __future__ import annotations + +import os +import sys +import shutil +import glob +from pathlib import Path +import sysconfig +import site +import re + + +REPO_ROOT = Path(__file__).resolve().parents[1] +SPEC_PATH = REPO_ROOT / "pyucc.spec" +DEST_DIR = REPO_ROOT / "pyucc" / "_internal" + + +def find_local_pygount_wheel() -> Path | None: + """Search common site-packages locations for pygount-*.whl and return first match.""" + candidates = [] + + # Preferred: current environment's site-packages (sysconfig) + try: + purelib = sysconfig.get_paths().get("purelib") + if purelib: + candidates.append(Path(purelib)) + except Exception: + pass + + # site.getsitepackages() may return multiple dirs + try: + for p in site.getsitepackages(): + candidates.append(Path(p)) + except Exception: + pass + + # Fallback: sys.path entries + for p in sys.path: + try: + candidates.append(Path(p)) + except Exception: + continue + + # Also check repository-local wheel folder and current working dir + try: + candidates.append(REPO_ROOT / "wheels") + except Exception: + pass + try: + candidates.append(Path.cwd()) + except Exception: + pass + try: + candidates.append(DEST_DIR) + except Exception: + pass + + seen = set() + for base in candidates: + if not base: + continue + base = base.resolve() + if str(base) in seen: + continue + seen.add(str(base)) + pattern = str(base / "pygount-*.whl") + for f in glob.glob(pattern): + if f: + return Path(f).resolve() + + return None + + +def copy_wheel_to_internal(wheel_path: Path) -> Path: + DEST_DIR.mkdir(parents=True, exist_ok=True) + dest = DEST_DIR / wheel_path.name + shutil.copy2(wheel_path, dest) + return dest + + +def patch_spec_add_datas(spec_path: Path, wheel_rel_path: str) -> bool: + """Insert a datas tuple ('', '_internal') into all datas=[...] lists in the spec. + + Returns True if file modified. + """ + text = spec_path.read_text(encoding="utf-8") + tuple_entry = f"('{wheel_rel_path}', '_internal')," + + if tuple_entry in text: + print("Spec already contains wheel datas entry; nothing to do.") + return False + + # Find all occurrences of 'datas=[' and insert the tuple after the opening bracket + new_text = text + inserts = 0 + idx = 0 + while True: + m = re.search(r"\bdatas\s*=\s*\[", new_text[idx:]) + if not m: + break + # m.start() relative to new_text[idx:] + start = idx + m.end() + # Insert after start + new_text = new_text[:start] + tuple_entry + new_text[start:] + inserts += 1 + idx = start + len(tuple_entry) + + if inserts: + backup = spec_path.with_suffix(spec_path.suffix + ".bak") + spec_path.replace(backup) + spec_path.write_text(new_text, encoding="utf-8") + print(f"Patched {spec_path} - inserted datas entry in {inserts} places (backup saved to {backup}).") + return True + + print(r"No datas=\[...] occurrences found in spec; no changes made.") + return False + + +def main(): + print(f"Repo root: {REPO_ROOT}") + + wheel = find_local_pygount_wheel() + if not wheel: + print("Could not locate a local pygount wheel in this environment.") + print("Please download a pygount wheel (pygount--py3-none-any.whl) into your environment or site-packages.") + sys.exit(2) + + print(f"Found pygount wheel: {wheel}") + + dest = copy_wheel_to_internal(wheel) + print(f"Copied wheel to: {dest}") + + # wheel_rel_path relative to project root (use forward slashes) + wheel_rel = f"pyucc/_internal/{dest.name}" + + if not SPEC_PATH.exists(): + print(f"Spec file not found at {SPEC_PATH}; please run this script from project root.") + sys.exit(3) + + changed = patch_spec_add_datas(SPEC_PATH, wheel_rel) + if changed: + print("Specification updated. Rebuild using: pyinstaller pyucc.spec") + else: + print("Specification unchanged.") + + +if __name__ == "__main__": + main() diff --git a/tools/check_pygount_build.py b/tools/check_pygount_build.py new file mode 100644 index 0000000..3c5bda1 --- /dev/null +++ b/tools/check_pygount_build.py @@ -0,0 +1,9 @@ +import sys, importlib +sys.path.insert(0, r'dist\\PyUcc') +from pyucc.core import countings_impl as c +print('HAS_PYGOUNT=', getattr(c, '_HAS_PYGOUNT', 'UNKNOWN')) +try: + m = importlib.import_module('pygount') + print('pygount import ok, version=', getattr(m, '__version__', '?')) +except Exception as e: + print('pygount import failed:', repr(e))