Files
TransPyC/lib/Projectrans/Utils.py

171 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import hashlib
import os
import ast
def compute_sha1(content: str) -> str:
return hashlib.sha1(content.encode('utf-8')).hexdigest()[:16]
def _check_annotation_for_export(annotation) -> bool:
"""递归检查类型注解中是否包含 CExport"""
if annotation is None:
return False
if isinstance(annotation, ast.Attribute):
if hasattr(annotation, 'attr') and annotation.attr == 'CExport':
return True
elif isinstance(annotation, ast.BinOp) and isinstance(annotation.op, ast.BitOr):
return _check_annotation_for_export(annotation.left) or _check_annotation_for_export(annotation.right)
elif isinstance(annotation, ast.Name):
return annotation.id == 'CExport'
return False
def sha1_file(filepath: str) -> str:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
return compute_sha1(content)
def get_file_dependencies(filepath: str, src_root: str) -> set:
"""解析 Python 文件的导入依赖"""
dependencies = set()
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
tree = ast.parse(content)
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
module = alias.name
dependencies.add(module)
elif isinstance(node, ast.ImportFrom):
if node.module:
module = node.module
if node.level > 0:
# from .module import name → 依赖 pkg.module模块本身
pkg_dir = os.path.dirname(filepath)
pkg_rel = os.path.relpath(pkg_dir, src_root).replace(os.sep, '.').replace('/', '.')
if pkg_rel == '.':
pkg_rel = ''
if pkg_rel:
dep_module = f'{pkg_rel}.{module}'
else:
dep_module = module
dependencies.add(dep_module)
else:
if not module.startswith('_'):
dependencies.add(module)
elif node.level > 0 and node.names:
# from . import name → 依赖 pkg.name子模块
for alias in node.names:
pkg_dir = os.path.dirname(filepath)
pkg_rel = os.path.relpath(pkg_dir, src_root).replace(os.sep, '.').replace('/', '.')
if pkg_rel == '.':
pkg_rel = ''
dep_module = f'{pkg_rel}.{alias.name}' if pkg_rel else alias.name
dependencies.add(dep_module)
except Exception as _e:
from lib.core.VLogger import get_logger as _vlog
from lib.constants.config import mode as _ConfigMode
if _ConfigMode == "strict":
raise
_vlog().warning(f"解析文件导入依赖失败: {_e}", "Exception")
return dependencies
def find_reachable_files_from_entries(src_root: str, entry_files: list) -> set:
"""从入口文件出发,找出所有可达的 .py 文件(通过导入关系)"""
all_files = {} # module_name -> filepath
for root, dirs, files in os.walk(src_root):
dirs[:] = [d for d in dirs if d not in ('__pycache__',)]
for file in files:
if file.endswith('.py'):
filepath = os.path.join(root, file)
rel = os.path.relpath(filepath, src_root)
module = os.path.splitext(rel)[0].replace(os.sep, '.').replace('/', '.')
if filepath.endswith('__init__.py'):
all_files[module] = filepath
parent_module = module.rsplit('.', 1)[0] if '.' in module else module
if parent_module not in all_files or not all_files[parent_module].endswith('__init__.py'):
all_files[parent_module] = filepath
elif module not in all_files:
all_files[module] = filepath
top_module = module.split('.')[0]
if top_module not in all_files:
all_files[top_module] = filepath
reachable = set()
queue = list(entry_files)
while queue:
current = queue.pop(0)
if current in reachable:
continue
reachable.add(current)
deps = get_file_dependencies(current, src_root)
for dep in deps:
if dep in all_files and all_files[dep] not in reachable:
queue.append(all_files[dep])
else:
for key, val in all_files.items():
if key.endswith('.' + dep) or key == dep:
if val not in reachable:
queue.append(val)
break
return reachable
def topological_sort_files(py_files: list, src_root: str) -> list:
"""对 Python 文件进行拓扑排序,确保依赖模块先被处理"""
# 构建模块名到文件路径的映射
module_to_file = {}
for filepath in py_files:
rel = os.path.relpath(filepath, src_root)
module = os.path.splitext(rel)[0].replace(os.sep, '.').replace('/', '.')
module_to_file[module] = filepath
# 也添加顶层模块名
top_module = module.split('.')[0]
if top_module not in module_to_file:
module_to_file[top_module] = filepath
# 构建依赖图
graph = {f: set() for f in py_files}
for filepath in py_files:
deps = get_file_dependencies(filepath, src_root)
for dep in deps:
if dep in module_to_file:
dep_file = module_to_file[dep]
if dep_file != filepath: # 避免自依赖
graph[filepath].add(dep_file)
# 拓扑排序 (Kahn's algorithm)
in_degree = {f: 0 for f in py_files}
for f, deps in graph.items():
for dep in deps:
if dep in in_degree:
in_degree[f] += 1
queue = [f for f, degree in in_degree.items() if degree == 0]
sorted_files = []
while queue:
# 按字母顺序处理同级别的文件,确保确定性
queue.sort()
current = queue.pop(0)
sorted_files.append(current)
for f, deps in graph.items():
if current in deps:
in_degree[f] -= 1
if in_degree[f] == 0:
queue.append(f)
# 如果有环,添加剩余文件
if len(sorted_files) < len(py_files):
remaining = [f for f in py_files if f not in sorted_files]
sorted_files.extend(remaining)
return sorted_files