Files

339 lines
11 KiB
Python
Raw Permalink Normal View History

2026-07-13 17:55:17 +08:00
import os
import shutil
import re
import xml.etree.ElementTree as ET
from pathlib import Path
from datetime import datetime
# =================================================
# 用户配置区(唯一需要用户关心的地方)
# =================================================
def get_project_root():
"""
获取用户输入的项目根目录并验证路径是否存在
"""
while True:
project_root = input("请输入你的工程根目录路径: ").strip()
if not project_root:
print("路径不能为空,请重新输入。")
continue
# 处理路径中的引号
if project_root.startswith('"') and project_root.endswith('"'):
project_root = project_root[1:-1]
elif project_root.startswith("'") and project_root.endswith("'"):
project_root = project_root[1:-1]
# 检查路径是否存在
if os.path.isdir(project_root):
return project_root
else:
print(f"错误: 路径 '{project_root}' 不存在,请重新输入。")
# =================================================
# Testbench 命名规则
# =================================================
TB_PATTERN = re.compile(r'(^tb_)|(_tb_)|(_tb\.)', re.IGNORECASE)
# =================================================
# 通用工具函数
# =================================================
def find_latest_xpr_in_dir(search_dir):
"""
在指定目录下查找最新修改的 xpr 文件
"""
files = [
os.path.join(search_dir, f)
for f in os.listdir(search_dir)
if f.lower().endswith('.xpr')
]
if not files:
raise FileNotFoundError(f"在目录 {search_dir} 下未找到 .xpr 文件")
return max(files, key=os.path.getmtime)
def load_extensions(config_path):
"""
config.txt 读取需要的文件后缀集合
"""
exts = set()
with open(config_path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip().lower()
if not line or line.startswith("#"):
continue
if not line.startswith("."):
line = "." + line
exts.add(line)
return exts
def is_testbench_filename(file_path):
"""
根据文件名判断是否是 testbench
"""
return bool(TB_PATTERN.search(Path(file_path).name.lower()))
# =================================================
# 模糊路径解析(核心)
# =================================================
def extract_tail_from_psrcdir(path_str):
"""
$PSRCDIR/xxx/yyy 提取 xxx/yyy
"""
marker = "$PSRCDIR/"
if marker not in path_str:
raise ValueError(f"路径中不包含 $PSRCDIR: {path_str}")
return path_str.split(marker, 1)[1].replace("\\", "/")
def find_file_by_tail(root_dir, tail_path):
"""
root_dir 下递归搜索
找到唯一一个以 tail_path 结尾的文件
"""
matches = []
tail_path = tail_path.replace("/", os.sep)
for dirpath, _, filenames in os.walk(root_dir):
for name in filenames:
full_path = os.path.join(dirpath, name)
if full_path.endswith(tail_path):
matches.append(full_path)
if not matches:
# 如果没找到文件,返回 None 表示未找到
return None
if len(matches) > 1:
# 如果找到多个匹配,返回第一个,但打印警告
print(f"警告: 发现多个匹配文件,将使用第一个:\n" + "\n".join(matches))
return matches[0]
return matches[0]
def resolve_xpr_path(path_str, project_root, xpr_file_path):
"""
xpr 中的 $PSRCDIR/... 解析为真实绝对路径
优先在与xpr文件同名的.srcs文件夹中查找如果找不到再在根目录下进行全局匹配
"""
if "$PSRCDIR/" not in path_str:
# 如果不包含 $PSRCDIR/ 标记,返回 None
return None
tail = extract_tail_from_psrcdir(path_str)
# 获取xpr文件名不含扩展名来构造.srcs目录名
xpr_filename = os.path.splitext(os.path.basename(xpr_file_path))[0]
srcs_dir = os.path.join(project_root, xpr_filename + ".srcs")
# 优先在.srcs目录中查找
if os.path.isdir(srcs_dir):
real_path = find_file_by_tail(srcs_dir, tail)
if real_path is not None:
return real_path
# 如果在.srcs目录中没找到则在根目录下进行全局匹配
return find_file_by_tail(project_root, tail)
# =================================================
# 核心逻辑
# =================================================
def extract_from_xpr_and_copy(xpr_path, config_path, project_root):
"""
xpr 中解析 DesignSrcs + synthesis 文件集合
使用模糊匹配解析真实路径并复制
"""
include_exts = load_extensions(config_path)
tree = ET.parse(xpr_path)
root = tree.getroot()
result_files = []
tb_files = []
failed_files = [] # 记录未找到的文件
# 只遍历 DesignSrcs
for fileset in root.iter("FileSet"):
if fileset.get("Type") != "DesignSrcs":
continue
for file_elem in fileset.findall("File"):
file_path = file_elem.get("Path")
if not file_path:
continue
# config.txt 后缀过滤 - 排除不需要的文件
suffix = Path(file_path).suffix.lower()
if include_exts and suffix not in include_exts:
continue
# testbench 命名判断
is_tb = is_testbench_filename(file_path)
fileinfo = file_elem.find("FileInfo")
if fileinfo is None:
continue
used_in_synth = False
auto_disabled = False
user_disabled = False
for attr in fileinfo.findall("Attr"):
name = attr.get("Name")
val = (attr.get("Val") or "").lower()
if name == "UsedIn" and val == "synthesis":
used_in_synth = True
elif name == "AutoDisabled" and val in ("1", "true"):
auto_disabled = True
elif name == "UserDisabled" and val in ("1", "true"):
user_disabled = True
if used_in_synth and not auto_disabled and not user_disabled:
real_path = resolve_xpr_path(file_path, project_root, xpr_path)
if real_path is not None:
if is_tb:
tb_files.append(real_path)
else:
result_files.append(real_path)
else:
failed_files.append(file_path) # 记录未找到的文件路径
print(f"解析并定位到 {len(result_files)} 个非testbench文件")
print(f"解析并定位到 {len(tb_files)} 个testbench文件")
if failed_files:
print(f"未能找到 {len(failed_files)} 个文件")
# -------------------------------------------------
# 创建输出目录(秒级时间戳)
# -------------------------------------------------
out_dir = datetime.now().strftime("Copy_Files_%Y%m%d_%H%M%S")
os.makedirs(out_dir)
print(f"创建输出目录: {out_dir}")
# -------------------------------------------------
# 创建tb文件目录如果存在tb文件
# -------------------------------------------------
tb_out_dir = None
if tb_files:
tb_out_dir = os.path.join(out_dir, "tb文件")
os.makedirs(tb_out_dir)
print(f"创建testbench输出目录: {tb_out_dir}")
# -------------------------------------------------
# 复制非testbench文件
# -------------------------------------------------
copied_normal = 0
skipped_normal = 0
for src in result_files:
if not os.path.exists(src):
print(f"文件不存在,跳过: {src}")
skipped_normal += 1
continue
base = os.path.basename(src)
dst = os.path.join(out_dir, base)
# 防止重名覆盖
if os.path.exists(dst):
name, ext = os.path.splitext(base)
idx = 1
while True:
new_dst = os.path.join(out_dir, f"{name}_{idx}{ext}")
if not os.path.exists(new_dst):
dst = new_dst
break
idx += 1
shutil.copy2(src, dst)
copied_normal += 1
# -------------------------------------------------
# 复制testbench文件
# -------------------------------------------------
copied_tb = 0
skipped_tb = 0
for src in tb_files:
if not os.path.exists(src):
print(f"testbench文件不存在跳过: {src}")
skipped_tb += 1
continue
base = os.path.basename(src)
dst = os.path.join(tb_out_dir, base) if tb_out_dir else os.path.join(out_dir, base)
# 防止重名覆盖
if os.path.exists(dst):
name, ext = os.path.splitext(base)
idx = 1
while True:
new_dst = os.path.join(tb_out_dir, f"{name}_{idx}{ext}") if tb_out_dir else os.path.join(out_dir, f"{name}_{idx}{ext}")
if not os.path.exists(new_dst):
dst = new_dst
break
idx += 1
shutil.copy2(src, dst)
copied_tb += 1
print("复制完成")
print(f" 非testbench文件:")
print(f" 成功复制: {copied_normal}")
print(f" 跳过文件: {skipped_normal}")
print(f" 未找到文件: {len(failed_files)}")
if failed_files:
print(" 未找到的文件列表:")
for failed_file in failed_files:
print(f" - {failed_file}")
print(f" 输出目录: {os.path.abspath(out_dir)}")
if tb_files:
print("\n")
print("识别到项目中存在testbench文件,请在vivado中检查是否为源代码")
print("")
# =================================================
# main
# =================================================
if __name__ == "__main__":
try:
PROJECT_ROOT = get_project_root()
if not os.path.isdir(PROJECT_ROOT):
raise RuntimeError(f"PROJECT_ROOT 不存在: {PROJECT_ROOT}")
xpr_file = find_latest_xpr_in_dir(PROJECT_ROOT)
config_file = os.path.join(os.getcwd(), "config.txt")
if not os.path.exists(config_file):
raise FileNotFoundError("当前目录下未找到 config.txt")
print(f"使用 xpr 文件 : {xpr_file}")
print(f"使用 config 文件: {config_file}")
print(f"项目根目录 : {PROJECT_ROOT}")
extract_from_xpr_and_copy(xpr_file, config_file, PROJECT_ROOT)
except Exception as e:
print("程序执行失败:")
print(e)
finally:
input("\n按回车键关闭窗口...")