first commit
This commit is contained in:
6
杂交版/config.txt
Normal file
6
杂交版/config.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
# 支持的文件类型(不区分大小写)
|
||||
# 每行一个后缀
|
||||
.v
|
||||
.vhd
|
||||
.vh
|
||||
.vhdl
|
||||
22
杂交版/使用说明.md
Normal file
22
杂交版/使用说明.md
Normal file
@@ -0,0 +1,22 @@
|
||||
1. 在 `config.txt` 中配置**需要提取的文件后缀**(每行一个),例如:
|
||||
```
|
||||
.v
|
||||
.vhd
|
||||
.vh
|
||||
.vhdl
|
||||
```
|
||||
|
||||
2. 运行脚本:
|
||||
```
|
||||
双击 源代码提取.exe
|
||||
将源代码所在项目的根目录路径输入到窗口,点击回车 (等待结果)
|
||||
**项目根目录下必须要有 `.xpr` / `.xise` / `.syr` 其中之一才能提取**。
|
||||
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 若项目为ise项目,根据.syr项目提取出的源代码可能包含IP文件,需要人工进行辨别 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 若项目为ise项目,根据.syr项目提取出的源代码可能包含IP文件,需要人工进行辨别 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 若项目为ise项目,根据.syr项目提取出的源代码可能包含IP文件,需要人工进行辨别 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
若项目根目录存在多个.xise或.syr文件,用户需要根据窗口提示选择正确的一个
|
||||
```
|
||||
713
杂交版/源代码提取.py
Normal file
713
杂交版/源代码提取.py
Normal file
@@ -0,0 +1,713 @@
|
||||
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 文件,未找到返回 None
|
||||
"""
|
||||
files = [
|
||||
os.path.join(search_dir, f)
|
||||
for f in os.listdir(search_dir)
|
||||
if f.lower().endswith('.xpr')
|
||||
]
|
||||
if not files:
|
||||
return None
|
||||
return max(files, key=os.path.getmtime)
|
||||
|
||||
|
||||
def find_latest_file_in_dir(search_dir, extension):
|
||||
"""
|
||||
在指定目录下查找最新修改的指定扩展名文件,未找到返回 None
|
||||
"""
|
||||
if not extension.startswith("."):
|
||||
extension = "." + extension
|
||||
files = [
|
||||
os.path.join(search_dir, f)
|
||||
for f in os.listdir(search_dir)
|
||||
if f.lower().endswith(extension.lower())
|
||||
]
|
||||
if not files:
|
||||
return None
|
||||
return max(files, key=os.path.getmtime)
|
||||
|
||||
|
||||
def find_all_files_in_dir(search_dir, extension):
|
||||
"""
|
||||
在指定目录下查找所有指定扩展名的文件,按修改时间降序排列
|
||||
"""
|
||||
if not extension.startswith("."):
|
||||
extension = "." + extension
|
||||
files = [
|
||||
os.path.join(search_dir, f)
|
||||
for f in os.listdir(search_dir)
|
||||
if f.lower().endswith(extension.lower())
|
||||
]
|
||||
files.sort(key=os.path.getmtime, reverse=True)
|
||||
return files
|
||||
|
||||
|
||||
def user_select_file(files, file_type):
|
||||
"""
|
||||
让用户从文件列表中选择一个,返回选中的文件路径
|
||||
如果只有一个文件则自动选择
|
||||
"""
|
||||
if len(files) == 1:
|
||||
print(f"找到 1 个 {file_type} 文件,自动选择: {os.path.basename(files[0])}")
|
||||
return files[0]
|
||||
|
||||
print(f"请选择需要解析的 {file_type} 文件:")
|
||||
for i, f in enumerate(files, 1):
|
||||
print(f" {i} {os.path.basename(f)}")
|
||||
|
||||
while True:
|
||||
try:
|
||||
choice = input("请输入对应编号: ").strip()
|
||||
idx = int(choice)
|
||||
if 1 <= idx <= len(files):
|
||||
return files[idx - 1]
|
||||
print(f"编号超出范围,请输入 1 ~ {len(files)}")
|
||||
except ValueError:
|
||||
print("输入无效,请输入数字编号")
|
||||
|
||||
|
||||
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/ 和 $PPRDIR/ 两种 Vivado 路径变量
|
||||
"""
|
||||
# 处理 $PPRDIR/ 路径(项目根目录下的相对路径)
|
||||
if "$PPRDIR/" in path_str:
|
||||
tail = path_str.split("$PPRDIR/", 1)[1].replace("\\", "/")
|
||||
direct_path = os.path.normpath(os.path.join(project_root, tail))
|
||||
if os.path.exists(direct_path):
|
||||
return direct_path
|
||||
return find_file_by_tail(project_root, tail)
|
||||
|
||||
# 处理 $PSRCDIR/ 路径(原有逻辑不变)
|
||||
if "$PSRCDIR/" not in path_str:
|
||||
return None
|
||||
|
||||
tail = extract_tail_from_psrcdir(path_str)
|
||||
|
||||
xpr_filename = os.path.splitext(os.path.basename(xpr_file_path))[0]
|
||||
srcs_dir = os.path.join(project_root, xpr_filename + ".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
|
||||
|
||||
return find_file_by_tail(project_root, tail)
|
||||
|
||||
|
||||
# =================================================
|
||||
# 通用 XML 工具
|
||||
# =================================================
|
||||
|
||||
def _get_attr_local(element, local_name):
|
||||
"""
|
||||
忽略命名空间,按属性本地名获取值
|
||||
(同时兼容带命名空间和不带命名空间的 XML)
|
||||
"""
|
||||
for key, value in element.attrib.items():
|
||||
if key == local_name or key.endswith("}" + local_name):
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def _get_tag_local(element):
|
||||
"""
|
||||
忽略命名空间,返回标签的本地名
|
||||
"""
|
||||
tag = element.tag
|
||||
return tag.split("}")[-1] if "}" in tag else tag
|
||||
|
||||
|
||||
# =================================================
|
||||
# 共享复制逻辑
|
||||
# =================================================
|
||||
|
||||
def _copy_extracted_files(result_files, tb_files, failed_files):
|
||||
"""
|
||||
将解析出的文件列表复制到输出目录(公共逻辑)
|
||||
"""
|
||||
# 创建输出目录
|
||||
out_dir = datetime.now().strftime("Copy_Files_%Y%m%d_%H%M%S")
|
||||
os.makedirs(out_dir)
|
||||
print(f"创建输出目录: {out_dir}")
|
||||
|
||||
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}")
|
||||
|
||||
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
|
||||
|
||||
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" testbench文件:")
|
||||
print(f" 成功复制: {copied_tb}")
|
||||
print(f" 跳过文件: {skipped_tb}")
|
||||
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("!!!!!!!!!!!!!!!!!!")
|
||||
|
||||
|
||||
# =================================================
|
||||
# 核心逻辑
|
||||
# =================================================
|
||||
|
||||
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)} 个文件")
|
||||
|
||||
_copy_extracted_files(result_files, tb_files, failed_files)
|
||||
|
||||
|
||||
# =================================================
|
||||
# ISE (.xise) 解析
|
||||
# =================================================
|
||||
|
||||
def extract_from_xise_and_copy(xise_path, config_path, project_root):
|
||||
"""
|
||||
从 .xise 中解析参与 Implementation 的源文件,
|
||||
如果存在 .syr 文件,取 (.xise ∩ .syr) 交集,只保留同时出现在两个列表中的文件
|
||||
"""
|
||||
include_exts = load_extensions(config_path)
|
||||
|
||||
tree = ET.parse(xise_path)
|
||||
root = tree.getroot()
|
||||
|
||||
# xise 解析结果:存原始路径(可能是相对也可能是绝对) + 解析后的绝对路径
|
||||
xise_result = [] # 原始路径(来自 xise 文件)
|
||||
xise_abs_map = {} # 原始路径 → 解析后的绝对路径(用于复制)
|
||||
xise_tb = []
|
||||
xise_tb_abs = {}
|
||||
xise_library = [] # 带 <library/> 标签的文件,始终保留
|
||||
xise_library_abs = {}
|
||||
xise_failed = []
|
||||
|
||||
for file_elem in root.iter():
|
||||
if _get_tag_local(file_elem) != "file":
|
||||
continue
|
||||
|
||||
file_path = _get_attr_local(file_elem, "name")
|
||||
if not file_path:
|
||||
continue
|
||||
|
||||
suffix = Path(file_path).suffix.lower()
|
||||
if include_exts and suffix not in include_exts:
|
||||
continue
|
||||
|
||||
is_tb = is_testbench_filename(file_path)
|
||||
|
||||
has_impl = False
|
||||
has_library = False
|
||||
for elem in file_elem.iter():
|
||||
tag = _get_tag_local(elem)
|
||||
if tag == "association":
|
||||
if _get_attr_local(elem, "name") == "Implementation":
|
||||
has_impl = True
|
||||
elif tag == "library":
|
||||
has_library = True
|
||||
|
||||
if not has_impl:
|
||||
continue
|
||||
|
||||
raw_path = file_path.replace("\\", "/")
|
||||
# 尝试用 project_root 拼接解析(相对路径和绝对路径都能处理)
|
||||
real_path = os.path.normpath(os.path.join(project_root, raw_path))
|
||||
if not os.path.exists(real_path):
|
||||
# 可能是从其他电脑拷过来的绝对路径,用文件名在 project_root 下搜索
|
||||
real_path = find_file_by_tail(project_root, os.path.basename(raw_path))
|
||||
|
||||
if real_path and os.path.exists(real_path):
|
||||
if is_tb:
|
||||
xise_tb.append(raw_path)
|
||||
xise_tb_abs[raw_path] = real_path
|
||||
else:
|
||||
xise_result.append(raw_path)
|
||||
xise_abs_map[raw_path] = real_path
|
||||
if has_library:
|
||||
xise_library.append(raw_path)
|
||||
xise_library_abs[raw_path] = real_path
|
||||
else:
|
||||
xise_failed.append(raw_path)
|
||||
|
||||
print(f"从 .xise 解析到 {len(xise_result)} 个非testbench文件")
|
||||
print(f"从 .xise 解析到 {len(xise_tb)} 个testbench文件")
|
||||
if xise_library:
|
||||
print(f"其中 {len(xise_library)} 个带 <library/> 标签,将始终保留")
|
||||
|
||||
# 查找 .syr 文件,如果有则提取并取交集
|
||||
syr_files = find_all_files_in_dir(project_root, ".syr")
|
||||
if syr_files:
|
||||
print(f"在项目根目录找到 {len(syr_files)} 个 .syr 文件")
|
||||
chosen_syr = user_select_file(syr_files, ".syr")
|
||||
print(f"使用 syr 文件进行二次过滤: {chosen_syr}")
|
||||
syr_result, syr_tb, syr_failed = _parse_syr(chosen_syr, config_path, project_root)
|
||||
|
||||
result_files = [] # 交集结果:xise 原始路径
|
||||
tb_files = []
|
||||
|
||||
# 第一阶段:严格匹配 —— 双方解析后的绝对路径做 normcase 比较
|
||||
syr_all_norm = set(os.path.normcase(f) for f in syr_result + syr_tb)
|
||||
for raw_path in xise_result:
|
||||
abs_path = xise_abs_map.get(raw_path)
|
||||
if abs_path and os.path.normcase(abs_path) in syr_all_norm:
|
||||
result_files.append(raw_path)
|
||||
for raw_path in xise_tb:
|
||||
abs_path = xise_tb_abs.get(raw_path)
|
||||
if abs_path and os.path.normcase(abs_path) in syr_all_norm:
|
||||
tb_files.append(raw_path)
|
||||
|
||||
# 第二阶段:尾部组件匹配(补配因绝对/相对路径差异导致未匹配的文件)
|
||||
# 注意:syr_all 必须包含 syr_failed 中的原始路径 ——
|
||||
# syr 里来自其他电脑的绝对路径 resolve 失败后进了 syr_failed,
|
||||
# 但它们的尾部仍然能和 xise 相对路径匹配上,必须参与比较
|
||||
xise_remaining = [f for f in xise_result if f not in result_files]
|
||||
xise_tb_remaining = [f for f in xise_tb if f not in tb_files]
|
||||
|
||||
if xise_remaining or xise_tb_remaining:
|
||||
syr_all = syr_result + syr_tb + syr_failed
|
||||
tail_matched = []
|
||||
matched_syr_failed = set()
|
||||
for raw_path in xise_remaining:
|
||||
for syr_path in syr_all:
|
||||
if paths_match_tail(raw_path, syr_path):
|
||||
result_files.append(raw_path)
|
||||
tail_matched.append(raw_path)
|
||||
if syr_path in syr_failed:
|
||||
matched_syr_failed.add(syr_path)
|
||||
break
|
||||
for raw_path in xise_tb_remaining:
|
||||
for syr_path in syr_all:
|
||||
if paths_match_tail(raw_path, syr_path):
|
||||
tb_files.append(raw_path)
|
||||
tail_matched.append(raw_path)
|
||||
if syr_path in syr_failed:
|
||||
matched_syr_failed.add(syr_path)
|
||||
break
|
||||
if tail_matched:
|
||||
print(f"尾部匹配补配 {len(tail_matched)} 个文件(绝对/相对路径差异)")
|
||||
|
||||
# 已匹配的 syr 路径从失败列表中移除,避免误导
|
||||
failed_files = xise_failed + [f for f in syr_failed if f not in matched_syr_failed]
|
||||
|
||||
print(f"\n交集后保留 {len(result_files)} 个非testbench文件")
|
||||
print(f"交集后保留 {len(tb_files)} 个testbench文件")
|
||||
else:
|
||||
result_files = xise_result
|
||||
tb_files = xise_tb
|
||||
failed_files = xise_failed
|
||||
print("项目根目录未找到 .syr 文件,直接使用 .xise 结果")
|
||||
|
||||
if failed_files:
|
||||
print(f"未能找到 {len(failed_files)} 个文件")
|
||||
|
||||
# 用 xise 侧映射重建绝对路径用于复制(xise/syr 中的绝对路径可能来自其他电脑,不可信)
|
||||
def _resolve_xise(raw_path, abs_map):
|
||||
if raw_path in abs_map:
|
||||
return abs_map[raw_path]
|
||||
# 兜底:直接用 project_root 拼接
|
||||
return os.path.normpath(os.path.join(project_root, raw_path.replace("\\", "/")))
|
||||
|
||||
result_files_abs = [_resolve_xise(f, xise_abs_map) for f in result_files]
|
||||
tb_files_abs = [_resolve_xise(f, xise_tb_abs) for f in tb_files]
|
||||
|
||||
# 合并带 <library/> 标签的文件(始终保留)
|
||||
for raw_path in xise_library:
|
||||
abs_path = _resolve_xise(raw_path, xise_library_abs)
|
||||
if abs_path not in result_files_abs and abs_path not in tb_files_abs:
|
||||
result_files_abs.append(abs_path)
|
||||
|
||||
_copy_extracted_files(result_files_abs, tb_files_abs, failed_files)
|
||||
|
||||
|
||||
# =================================================
|
||||
# ISE (.syr) 解析
|
||||
# =================================================
|
||||
|
||||
SYR_PATTERN = re.compile(r'Compiling\s+\w+\s+file\s+"([^"]+)"', re.IGNORECASE)
|
||||
|
||||
|
||||
def paths_match_tail(path_a, path_b):
|
||||
"""
|
||||
判断两个路径的尾部组件是否匹配。
|
||||
将路径拆分为组件列表,检查较短列表是否为较长列表的后缀。
|
||||
无论绝对/相对路径、正斜杠/反斜杠、盘符差异,都能正确匹配。
|
||||
|
||||
例: ../src/pro/xxx.v vs C:/Users/.../src/pro/xxx.v → 匹配(尾部 src/pro/xxx.v 相同)
|
||||
./xxx.v vs F:/project/xxx.v → 匹配
|
||||
xxx.v vs F:/project/xxx.v → 匹配
|
||||
"""
|
||||
def _normalize_parts(p):
|
||||
p = p.replace("\\", "/").lower()
|
||||
if len(p) >= 2 and p[1] == ":":
|
||||
p = p[2:]
|
||||
while p.startswith("../"):
|
||||
p = p[3:]
|
||||
while p.startswith("./"):
|
||||
p = p[2:]
|
||||
p = p.strip("/")
|
||||
return p.split("/") if p else []
|
||||
|
||||
parts_a = _normalize_parts(path_a)
|
||||
parts_b = _normalize_parts(path_b)
|
||||
|
||||
if not parts_a or not parts_b:
|
||||
return False
|
||||
|
||||
if len(parts_a) <= len(parts_b):
|
||||
return parts_a == parts_b[-len(parts_a):]
|
||||
else:
|
||||
return parts_b == parts_a[-len(parts_b):]
|
||||
|
||||
|
||||
def resolve_syr_path(extracted_path, project_root):
|
||||
"""
|
||||
从 .syr 中提取的路径解析为真实绝对路径
|
||||
支持绝对路径 / 相对路径 / 纯文件名
|
||||
"""
|
||||
extracted_path = extracted_path.replace("\\", "/").strip()
|
||||
|
||||
if os.path.isabs(extracted_path):
|
||||
if os.path.exists(extracted_path):
|
||||
return os.path.normpath(extracted_path)
|
||||
filename = os.path.basename(extracted_path)
|
||||
if os.path.exists(os.path.join(project_root, filename)):
|
||||
return os.path.normpath(os.path.join(project_root, filename))
|
||||
|
||||
# 相对路径:和 xise 一样用 os.path.join + normpath,保留完整目录结构
|
||||
real_path = os.path.normpath(os.path.join(project_root, extracted_path))
|
||||
if os.path.exists(real_path):
|
||||
return real_path
|
||||
|
||||
# 回退:用文件名模糊搜索
|
||||
tail = os.path.basename(extracted_path)
|
||||
return find_file_by_tail(project_root, tail)
|
||||
|
||||
|
||||
def _parse_syr(syr_path, config_path, project_root):
|
||||
"""
|
||||
从 .syr 综合报告中解析源文件,返回 (result_files, tb_files, failed_files)
|
||||
不执行复制,供内部调用做交集等操作
|
||||
"""
|
||||
include_exts = load_extensions(config_path)
|
||||
|
||||
result_files = []
|
||||
tb_files = []
|
||||
failed_files = []
|
||||
seen_paths = set()
|
||||
|
||||
with open(syr_path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
for line in f:
|
||||
matches = SYR_PATTERN.findall(line)
|
||||
for extracted_path in matches:
|
||||
if extracted_path in seen_paths:
|
||||
continue
|
||||
seen_paths.add(extracted_path)
|
||||
|
||||
suffix = Path(extracted_path).suffix.lower()
|
||||
if include_exts and suffix not in include_exts:
|
||||
continue
|
||||
|
||||
is_tb = is_testbench_filename(extracted_path)
|
||||
|
||||
real_path = resolve_syr_path(extracted_path, project_root)
|
||||
if real_path is not None and os.path.exists(real_path):
|
||||
if is_tb:
|
||||
tb_files.append(real_path)
|
||||
else:
|
||||
result_files.append(real_path)
|
||||
else:
|
||||
failed_files.append(extracted_path)
|
||||
|
||||
return result_files, tb_files, failed_files
|
||||
|
||||
|
||||
def extract_from_syr_and_copy(syr_path, config_path, project_root):
|
||||
"""
|
||||
从 .syr 综合报告中解析源文件并复制
|
||||
匹配模式: Compiling vhdl file "path/to/file.vhd" in Library work
|
||||
"""
|
||||
result_files, tb_files, failed_files = _parse_syr(syr_path, config_path, project_root)
|
||||
|
||||
print(f"解析并定位到 {len(result_files)} 个非testbench文件")
|
||||
print(f"解析并定位到 {len(tb_files)} 个testbench文件")
|
||||
if failed_files:
|
||||
print(f"未能找到 {len(failed_files)} 个文件")
|
||||
|
||||
_copy_extracted_files(result_files, tb_files, failed_files)
|
||||
|
||||
|
||||
# =================================================
|
||||
# main
|
||||
# =================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
PROJECT_ROOT = get_project_root()
|
||||
if not os.path.isdir(PROJECT_ROOT):
|
||||
raise RuntimeError(f"PROJECT_ROOT 不存在: {PROJECT_ROOT}")
|
||||
|
||||
config_file = os.path.join(os.getcwd(), "config.txt")
|
||||
if not os.path.exists(config_file):
|
||||
raise FileNotFoundError("当前目录下未找到 config.txt")
|
||||
|
||||
print(f"使用 config 文件: {config_file}")
|
||||
print(f"项目根目录 : {PROJECT_ROOT}")
|
||||
|
||||
# 优先级: .xpr → .xise → .syr
|
||||
xpr_file = find_latest_xpr_in_dir(PROJECT_ROOT)
|
||||
if xpr_file:
|
||||
print(f"使用 xpr 文件 : {xpr_file}")
|
||||
extract_from_xpr_and_copy(xpr_file, config_file, PROJECT_ROOT)
|
||||
else:
|
||||
xise_files = find_all_files_in_dir(PROJECT_ROOT, ".xise")
|
||||
if xise_files:
|
||||
chosen_xise = user_select_file(xise_files, ".xise")
|
||||
print(f"使用 xise 文件 : {chosen_xise}")
|
||||
extract_from_xise_and_copy(chosen_xise, config_file, PROJECT_ROOT)
|
||||
else:
|
||||
syr_files = find_all_files_in_dir(PROJECT_ROOT, ".syr")
|
||||
if syr_files:
|
||||
chosen_syr = user_select_file(syr_files, ".syr")
|
||||
print(f"使用 syr 文件 : {chosen_syr}")
|
||||
extract_from_syr_and_copy(chosen_syr, config_file, PROJECT_ROOT)
|
||||
else:
|
||||
print("当前项目下没有能提取源代码的文件依据")
|
||||
|
||||
except Exception as e:
|
||||
print("程序执行失败:")
|
||||
print(e)
|
||||
|
||||
finally:
|
||||
input("\n按回车键关闭窗口...")
|
||||
6
杂交版/源代码提取/config.txt
Normal file
6
杂交版/源代码提取/config.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
# 支持的文件类型(不区分大小写)
|
||||
# 每行一个后缀
|
||||
.v
|
||||
.vhd
|
||||
.vh
|
||||
.vhdl
|
||||
22
杂交版/源代码提取/使用说明.md
Normal file
22
杂交版/源代码提取/使用说明.md
Normal file
@@ -0,0 +1,22 @@
|
||||
1. 在 `config.txt` 中配置**需要提取的文件后缀**(每行一个),例如:
|
||||
```
|
||||
.v
|
||||
.vhd
|
||||
.vh
|
||||
.vhdl
|
||||
```
|
||||
|
||||
2. 运行脚本:
|
||||
```
|
||||
双击 源代码提取.exe
|
||||
将源代码所在项目的根目录路径输入到窗口,点击回车 (等待结果)
|
||||
**项目根目录下必须要有 `.xpr` / `.xise` / `.syr` 其中之一才能提取**。
|
||||
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 若项目为ise项目,根据.syr项目提取出的源代码可能包含IP文件,需要人工进行辨别 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 若项目为ise项目,根据.syr项目提取出的源代码可能包含IP文件,需要人工进行辨别 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 若项目为ise项目,根据.syr项目提取出的源代码可能包含IP文件,需要人工进行辨别 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
若项目根目录存在多个.xise或.syr文件,用户需要根据窗口提示选择正确的一个
|
||||
```
|
||||
BIN
杂交版/源代码提取/源代码提取.exe
Normal file
BIN
杂交版/源代码提取/源代码提取.exe
Normal file
Binary file not shown.
Reference in New Issue
Block a user