From efb618ab0d3252947a31b7f5273b90ce8b12ee8e Mon Sep 17 00:00:00 2001 From: xqyjlj Date: Sat, 13 Dec 2025 23:55:50 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9E=20fix:=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E7=94=9F=E6=88=90=E5=A4=9A=E4=B8=AA=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=97=B6=E5=85=B6filters=E8=A2=AB=E6=B7=B7=E7=94=A8?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xqyjlj --- server/coder/coder.py | 42 ++++++----- server/coder/generator_loader.py | 126 +++++++++++++++++++++++++++++++ server/utils/io.py | 13 +++- server/utils/sys.py | 9 +-- 4 files changed, 164 insertions(+), 26 deletions(-) create mode 100644 server/coder/generator_loader.py diff --git a/server/coder/coder.py b/server/coder/coder.py index 6a2fc6de..80ebd8ac 100644 --- a/server/coder/coder.py +++ b/server/coder/coder.py @@ -25,9 +25,7 @@ # -import glob import hashlib -import importlib.util import os import re import sys @@ -44,6 +42,7 @@ from utils.sys import SYS_UTILS from .filters import FILTERS +from .generator_loader import GeneratorLoader class Coder: @@ -59,7 +58,15 @@ def __init__(self, project: Project, summary: Summary): "generate": Signal("generate"), } - sys.path = SYS_UTILS.sys_path() + [f"{project.hal_folder()}/tools/generator"] + hal = self._project.gen.hal + halVersion = self._project.gen.halVersion + hal_folder = Path(self._project.hal_folder()) + + self._package_name = f"{hal}_{halVersion}_generator" + self._filters_package_name = f"{self._package_name}.filters" + self._generator_folder = hal_folder / "tools" / "generator" + self._filters_folder = self._generator_folder / "filters" + self._generator = self._load_generator() self.files_table = self._get_files_table() @@ -167,16 +174,15 @@ def files_list(self) -> list[str]: return list(self.files_table.keys()) def _load_generator(self) -> ModuleType | None: - if self._check_hal_folder(): - spec = importlib.util.spec_from_file_location( - "coder", f"{self._project.hal_folder()}/tools/generator/generator.py" - ) - if spec is None or spec.loader is None: - return None - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - return None + if not self._check_hal_folder(): + return None + + try: + loader = GeneratorLoader(self._package_name, self._generator_folder) + return loader.load() + except Exception as e: + logger.error(f"Failed to load generator: {e}") + return None def _get_files_table(self) -> dict[str, dict[str, str]]: if self._generator is None: @@ -313,13 +319,11 @@ def _get_environment(self) -> jinja2.Environment: env.add_extension("jinja2.ext.do") env.add_extension("jinja2.ext.loopcontrols") - files = glob.glob(f"{package_folder}/tools/generator/filters/*.py") + files = self._filters_folder.glob("*.py") for file in files: - spec = importlib.util.spec_from_file_location(Path(file).stem, file) - if spec is None or spec.loader is None: - break - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) + stem = file.stem + full_name = f"{self._filters_package_name}.{stem}" + module = sys.modules[full_name] functions = [ name for name in dir(module) if callable(getattr(module, name)) ] diff --git a/server/coder/generator_loader.py b/server/coder/generator_loader.py new file mode 100644 index 00000000..f8d1e3c1 --- /dev/null +++ b/server/coder/generator_loader.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- + +# Licensed under the Apache License v. 2 (the "License") +# You may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0.html +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (C) 2025-2025 xqyjlj +# +# @author xqyjlj +# @file generator_loader.py +# +# Change Logs: +# Date Author Notes +# ------------ ---------- ----------------------------------------------- +# 2025-07-07 xqyjlj initial version +# + +import importlib.util +import sys +from pathlib import Path +from types import ModuleType +from typing import Dict, Optional + +from loguru import logger +from utils.io import IO_UTILS + + +class GeneratorLoader: + + def __init__(self, name: str, folder: Path): + self._package_name = name + self._folder = folder + self._filters_folder = self._folder / "filters" + self._filters_package_name = f"{name}.filters" + self._file_sha1_cache: Dict[str, str] = {} + + def load(self) -> Optional[ModuleType]: + self._create_packages() + self._load_filters() + return self._load_generator() + + def _create_packages(self) -> None: + if self._package_name not in sys.modules: + pkg = ModuleType(self._package_name) + pkg.__path__ = [str(self._folder)] + sys.modules[self._package_name] = pkg + + if self._filters_package_name not in sys.modules: + filters_pkg = ModuleType(self._filters_package_name) + filters_pkg.__path__ = [str(self._filters_folder)] + sys.modules[self._filters_package_name] = filters_pkg + + def _load_filters(self) -> None: + if not self._filters_folder.exists(): + return + for file in self._filters_folder.glob("*.py"): + stem = file.stem + full_name = f"{self._filters_package_name}.{stem}" + try: + if full_name in sys.modules and not self._is_file_changed(file): + continue + + if full_name in sys.modules: + del sys.modules[full_name] + + self._load_module(full_name, file, [str(self._filters_folder)]) + except Exception as e: + logger.warning(f"Failed to load filter {file}: {e}") + + def _load_generator(self) -> Optional[ModuleType]: + gen_file = self._folder / "generator.py" + if not gen_file.exists(): + return None + + try: + if self._package_name in sys.modules and not self._is_file_changed( + gen_file + ): + return sys.modules[self._package_name] + + if self._package_name in sys.modules: + del sys.modules[self._package_name] + + return self._load_module(self._package_name, gen_file, [str(self._folder)]) + except Exception as e: + logger.error(f"Failed to load generator: {e}") + return None + + def _load_module(self, name: str, file: Path, search_paths: list) -> ModuleType: + spec = importlib.util.spec_from_file_location( + name, file, submodule_search_locations=search_paths + ) + if spec is None or spec.loader is None: + raise ImportError(f"Could not create spec for {file}") + + module = importlib.util.module_from_spec(spec) + sys.modules[name] = module + spec.loader.exec_module(module) + return module + + def _is_file_changed(self, file_path: Path) -> bool: + file_key = str(file_path.absolute()) + current_sha1 = IO_UTILS.sha1(file_path) + + if file_key not in self._file_sha1_cache: + self._file_sha1_cache[file_key] = current_sha1 + return True + + if self._file_sha1_cache[file_key] != current_sha1: + self._file_sha1_cache[file_key] = current_sha1 + return True + + return False + + @property + def filters_package_name(self) -> str: + return self._filters_package_name diff --git a/server/utils/io.py b/server/utils/io.py index b6b28ffb..d3b621a1 100644 --- a/server/utils/io.py +++ b/server/utils/io.py @@ -25,7 +25,10 @@ # +import hashlib import os +from pathlib import Path +from typing import Any from ruamel.yaml import YAML @@ -39,10 +42,18 @@ def readlines(path: str) -> list[str]: return f.readlines() @staticmethod - def read_yaml(file: str): + def read_yaml(file: str) -> Any: with open(file, "r", encoding="utf-8") as f: yaml = YAML() return yaml.load(f.read()) + @staticmethod + def sha1(file: Path) -> str: + hash_sha1 = hashlib.sha1() + with open(file, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_sha1.update(chunk) + return hash_sha1.hexdigest() + IO_UTILS = IoUtils() diff --git a/server/utils/sys.py b/server/utils/sys.py index 443f9101..0ddbb5da 100644 --- a/server/utils/sys.py +++ b/server/utils/sys.py @@ -24,18 +24,15 @@ # 2025-07-07 xqyjlj initial version # -import copy + import sys from pathlib import Path class SysUtils: def __init__(self): - self._sys_path = copy.deepcopy(sys.path) - self._sys_path.append(self.public_folder()) - - def sys_path(self) -> list[str]: - return self._sys_path + if sys.path[-1] != self.public_folder(): + sys.path.append(self.public_folder()) @staticmethod def exe_folder() -> str: