From 1bbbd0976200ee35a36db90b93c41cecf164114f Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Mon, 1 Dec 2025 08:29:48 +0100 Subject: [PATCH] Aggiunta gestione submoduli --- projectutility/config/tool_state.json | 4 +- projectutility/config/tools_registry.json | 89 +++++++++++++++++++++-- projectutility/core/git_manager.py | 43 +++++++++++ projectutility/core/registry_models.py | 3 + projectutility/core/submodule_manager.py | 74 +++++++++++++++++++ projectutility/gui/main_window.py | 50 +------------ 6 files changed, 205 insertions(+), 58 deletions(-) create mode 100644 projectutility/core/submodule_manager.py diff --git a/projectutility/config/tool_state.json b/projectutility/config/tool_state.json index 200d5e1..4c7f17e 100644 --- a/projectutility/config/tool_state.json +++ b/projectutility/config/tool_state.json @@ -25,5 +25,7 @@ "pyinstaller_gui_git": {}, "geoelevation_git": {}, "dependencyanalizer_git": {}, - "dependencyanalyzer_git": {} + "dependencyanalyzer_git": {}, + "git_utility_git": {}, + "pyucc_git": {} } \ No newline at end of file diff --git a/projectutility/config/tools_registry.json b/projectutility/config/tools_registry.json index ab7bc7f..6314222 100644 --- a/projectutility/config/tools_registry.json +++ b/projectutility/config/tools_registry.json @@ -5,7 +5,7 @@ "description": "Generates .ico files from PNG images, fetched via Git.", "type": "git", "enabled": true, - "git_url": "http://192.168.100.10:3000/VALLONGOL/CreateIconFromFilesPng", + "git_url": "http://192.168.100.10:3000/VALLONGOL/SXXXXXXX_CreateIconFromFilesPng", "git_ref": "master", "run_command": ["python", "create_icon_file.py"], "has_gui": false, @@ -17,7 +17,7 @@ "description": "GUI Wrapper for PyInstaller, fetched via Git.", "type": "git", "enabled": true, - "git_url": "http://192.168.100.10:3000/VALLONGOL/PyInstallerGUIWrapper", + "git_url": "http://192.168.100.10:3000/VALLONGOL/SXXXXXXX_PyInstallerGUIWrapper", "git_ref": "master", "run_command": ["python", "-m", "pyinstallerguiwrapper"], "has_gui": true, @@ -29,7 +29,7 @@ "description": "Get elevation data from network, fetched via Git.", "type": "git", "enabled": true, - "git_url": "http://192.168.100.10:3000/VALLONGOL/GeoElevation", + "git_url": "http://192.168.100.10:3000/VALLONGOL/SXXXXXXX_GeoElevation", "git_ref": "master", "run_command": ["python", "-m", "geoelevation"], "has_gui": true, @@ -41,7 +41,7 @@ "description": "Manag. requirements from python project, fetched via Git.", "type": "git", "enabled": true, - "git_url": "http://192.168.100.10:3000/VALLONGOL/DependencyAnalyzer", + "git_url": "http://192.168.100.10:3000/VALLONGOL/SXXXXXXX_DependencyAnalyzer", "git_ref": "master", "run_command": ["python", "-m", "dependencyanalyzer"], "has_gui": true, @@ -53,7 +53,7 @@ "description": "Create standard folders for new dev tool, fetched via Git.", "type": "git", "enabled": true, - "git_url": "http://192.168.100.10:3000/VALLONGOL/ProjectInitializer", + "git_url": "http://192.168.100.10:3000/VALLONGOL/SXXXXXXX_ProjectInitializer", "git_ref": "master", "run_command": ["python", "-m", "projectinitializer"], "has_gui": true, @@ -65,7 +65,7 @@ "description": "Gui for console application g_reconverter, fetched via Git.", "type": "git", "enabled": true, - "git_url": "http://192.168.100.10:3000/VALLONGOL/GUI_g_reconverter", + "git_url": "http://192.168.100.10:3000/VALLONGOL/SXXXXXXX_GUI_g_reconverter", "git_ref": "master", "run_command": ["python", "-m", "gui_g_reconverter"], "has_gui": true, @@ -89,7 +89,7 @@ "description": "Backup suite, fetched via Git.", "type": "git", "enabled": true, - "git_url": "http://192.168.100.10:3000/VALLONGOL/BackupTools", + "git_url": "http://192.168.100.10:3000/VALLONGOL/SXXXXXXX_BackupTools", "git_ref": "master", "run_command": ["python", "-m", "backuptools"], "has_gui": true, @@ -101,12 +101,85 @@ "description": "Launcher tool, fetched via Git.", "type": "git", "enabled": true, - "git_url": "http://192.168.100.10:3000/VALLONGOL/LauncherTool", + "git_url": "http://192.168.100.10:3000/VALLONGOL/SXXXXXXX_LauncherTool", "git_ref": "master", "run_command": ["python", "-m", "launchertool"], "has_gui": true, "parameters_definition_file": null }, + { + "id": "py_hasher_git", + "display_name": "PyHasher(Git)", + "description": "PyHasher, fetched via Git.", + "type": "git", + "enabled": true, + "git_url": "http://192.168.100.10:3000/VALLONGOL/SXXXXXXX_PyHasher", + "git_ref": "master", + "run_command": ["python", "-m", "pyhasher"], + "has_gui": true, + "parameters_definition_file": null + }, + { + "id": "repo_sync_git", + "display_name": "RepoSync (Git)", + "description": "Reporter sync, fetched via Git.", + "type": "git", + "enabled": true, + "git_url": "http://192.168.100.10:3000/VALLONGOL/SXXXXXXX_RepoSync", + "git_ref": "master", + "run_command": ["python", "-m", "reposync"], + "has_gui": true, + "parameters_definition_file": null + }, + { + "id": "profile_analyzer_git", + "display_name": "Profile Analyzer (Git)", + "description": "View profile files, fetched via Git.", + "type": "git", + "enabled": true, + "git_url": "http://192.168.100.10:3000/VALLONGOL/SXXXXXXX_ProfileAnalyzer", + "git_ref": "master", + "run_command": ["python", "-m", "profileanalyzer"], + "has_gui": true, + "parameters_definition_file": null + }, + { + "id": "markdown_converter_git", + "display_name": "Markdown Converter (Git)", + "description": "Markdown Converter into pdf and docx, fetched via Git.", + "type": "git", + "enabled": true, + "git_url": "http://192.168.100.10:3000/VALLONGOL/SXXXXXXX_MarkdownConverter", + "git_ref": "master", + "run_command": ["python", "-m", "markdownconverter"], + "has_gui": true, + "parameters_definition_file": null + }, + { + "id": "pyucc_git", + "display_name": "Py UCC (Git)", + "description": "Py UCC, fetched via Git.", + "type": "git", + "enabled": true, + "git_url": "http://192.168.100.10:3000/VALLONGOL/SXXXXXXX_PyUCC", + "git_ref": "master", + "run_command": ["python", "-m", "pyucc"], + "has_gui": true, + "parameters_definition_file": null, + "auto_update_submodules": true, + "submodules": [ + { + "name": "python-resource-monitor", + "path": "external/python-resource-monitor", + "modules": ["resource_monitor.py"] + }, + { + "name": "python-tkinter-logger", + "path": "external/python-tkinter-logger", + "modules": ["tkinter_logger.py"] + } + ] + }, { "id": "dummy_script", "display_name": "Dummy Script Runner (Local)", diff --git a/projectutility/core/git_manager.py b/projectutility/core/git_manager.py index 219ce90..2d79f05 100644 --- a/projectutility/core/git_manager.py +++ b/projectutility/core/git_manager.py @@ -573,6 +573,28 @@ def update_repository( f"Error in progress callback (post-pull success): {cb_err}", exc_info=True, ) + + # --- Submodule handling (if configured) --- + try: + if getattr(tool_entry, "auto_update_submodules", False) or getattr( + tool_entry, "submodules", None + ): + local_path = get_local_repo_path(tool_id, tool_entry.local_dir_name) + try: + from projectutility.core.submodule_manager import ensure_submodules + + ok = ensure_submodules(local_path, tool_entry.submodules) + if not ok: + logger.warning( + f"[{tool_id}] Submodule update returned non-ok status." + ) + except Exception as e: + logger.exception( + f"[{tool_id}] Failed to run submodule update: {e}" + ) + except Exception as e: + logger.exception(f"[{tool_id}] Error checking submodule config: {e}") + return ( True, f"Repo updated. On branch '{current_branch_name}' tracking '{target_ref}'.", @@ -617,6 +639,27 @@ def update_repository( logger.info( f"[{tool_id}] Checkout to detached HEAD at {detached_commit_sha} ('{target_ref}'). Update complete." ) + # Try submodule update for detached HEAD too + try: + if getattr(tool_entry, "auto_update_submodules", False) or getattr( + tool_entry, "submodules", None + ): + local_path = get_local_repo_path(tool_id, tool_entry.local_dir_name) + try: + from projectutility.core.submodule_manager import ensure_submodules + + ok = ensure_submodules(local_path, tool_entry.submodules) + if not ok: + logger.warning( + f"[{tool_id}] Submodule update returned non-ok status (detached HEAD)." + ) + except Exception as e: + logger.exception( + f"[{tool_id}] Failed to run submodule update (detached HEAD): {e}" + ) + except Exception: + logger.exception(f"[{tool_id}] Error checking submodule config (detached HEAD)") + return ( True, f"Repo at '{target_ref}' (Detached HEAD {detached_commit_sha}).", diff --git a/projectutility/core/registry_models.py b/projectutility/core/registry_models.py index a97f8c9..da3bf79 100644 --- a/projectutility/core/registry_models.py +++ b/projectutility/core/registry_models.py @@ -43,6 +43,9 @@ class ToolRegistryEntry: local_dir_name: Optional[str] = ( None # Optional custom name for the local directory in 'managed_tools'. Defaults to 'id' if None. ) + # --- Submodule support --- + auto_update_submodules: bool = False # If true, attempt to init/update submodules automatically + submodules: Optional[List[Dict[str, Any]]] = None # --- Parameter Definition Source --- # For type='local', this can be an inline list of parameter dicts. diff --git a/projectutility/core/submodule_manager.py b/projectutility/core/submodule_manager.py new file mode 100644 index 0000000..4adf16a --- /dev/null +++ b/projectutility/core/submodule_manager.py @@ -0,0 +1,74 @@ +"""projectutility.core.submodule_manager + +Utilities to ensure Git submodules are initialized/updated for a tool repo. + +This module uses the system `git` command via subprocess to avoid +adding a hard dependency on GitPython for submodule operations. +""" +import logging +import os +import subprocess +from typing import List, Optional, Dict + +logger = logging.getLogger(__name__) + + +def ensure_submodules( + repo_root: str, submodules: Optional[List[Dict[str, str]]] = None, timeout: int = 300 +) -> bool: + """Ensure submodules are initialized and updated for a repository. + + Args: + repo_root: absolute path to the repository root (where .git lives). + submodules: optional list of dicts describing submodules. Each dict may + contain a `path` key (relative path inside repo). If None, + this will attempt to update the whole `external` directory + if it exists, otherwise run a global submodule update. + timeout: max seconds to wait for the git command. + + Returns: + True if git submodule update completed successfully, False otherwise. + """ + if not os.path.isdir(repo_root): + logger.error("Submodule update: repo_root not found: %s", repo_root) + return False + + cmd = ["git", "-C", repo_root, "submodule", "update", "--init", "--recursive"] + + # Build path list for git command + if submodules: + paths = [] + for entry in submodules: + p = entry.get("path") if isinstance(entry, dict) else None + if p: + paths.append(p) + if paths: + cmd += paths + else: + # Fallback: if repo has 'external' directory, limit to it + external = os.path.join(repo_root, "external") + if os.path.isdir(external): + cmd += ["external"] + + logger.info("Running submodule update: %s", " ".join(cmd)) + try: + proc = subprocess.run( + cmd, check=True, capture_output=True, text=True, timeout=timeout + ) + if proc.stdout: + logger.debug("git submodule stdout: %s", proc.stdout) + if proc.stderr: + logger.debug("git submodule stderr: %s", proc.stderr) + logger.info("Submodule update completed for %s", repo_root) + return True + except subprocess.CalledProcessError as e: + logger.error( + "git submodule update failed for %s: %s", repo_root, e.stderr or str(e) + ) + return False + except subprocess.TimeoutExpired: + logger.error("git submodule update timeout for %s", repo_root) + return False + except Exception as e: + logger.exception("Unexpected error while updating submodules for %s: %s", repo_root, e) + return False diff --git a/projectutility/gui/main_window.py b/projectutility/gui/main_window.py index 05fa4b2..5108747 100644 --- a/projectutility/gui/main_window.py +++ b/projectutility/gui/main_window.py @@ -164,54 +164,6 @@ except ImportError as e: ) # Ensure flag is False and constants remain dummies GIT_ENABLED = False - # Define dummy class if import failed completely - class git_manager: - pass# ridefinito come dummy se fallisce import - # ... (definizione dummy di git_manager) - -except Exception as e: - logging.getLogger(__name__).error( - f"Unexpected error importing git_manager: {e}. Git features disabled.", - exc_info=True - ) - GIT_ENABLED = False - -try: - # Absolute import - from projectutility.core import git_manager - - GIT_ENABLED = git_manager.GITPYTHON_AVAILABLE - - if GIT_ENABLED: - # Import real constants only if GitPython is available - from projectutility.core.git_manager import ( - GIT_STATUS_UP_TO_DATE, - GIT_STATUS_BEHIND, - GIT_STATUS_AHEAD, - GIT_STATUS_DIVERGED, - GIT_STATUS_NOT_CLONED, - GIT_STATUS_ERROR, - GIT_STATUS_CHECKING, - GIT_STATUS_UPDATING, - GIT_STATUS_GITPYTHON_MISSING, - ) - - logging.getLogger(__name__).info( - "Successfully imported git_manager (GitPython available)." - ) - else: - logging.getLogger(__name__).warning( - "Imported git_manager, but GitPython library not found. Git features disabled." - ) - # Constants keep their default dummy values defined above - -except ImportError as e: - logging.getLogger(__name__).error( - f"Failed to import core.git_manager: {e}. Git features disabled.", exc_info=True - ) - # Ensure flag is False and constants remain dummies - GIT_ENABLED = False - # Define dummy class if import failed completely class git_manager: GITPYTHON_AVAILABLE = False @@ -238,7 +190,7 @@ except ImportError as e: except Exception as e: logging.getLogger(__name__).error( f"Unexpected error importing git_manager: {e}. Git features disabled.", - exc_info=True, + exc_info=True ) GIT_ENABLED = False