339 lines
11 KiB
Python
339 lines
11 KiB
Python
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按回车键关闭窗口...") |