feat: Xilinx Kit directory selector + multi-version scan

Configuration panel now has a 'Xilinx Kit:' row at the top with:
- Entry field showing current path
- [Browse] button using system directory dialog (cross-platform)
- Select root directory (e.g. /data/xilinx or C:/Xilinx)

vitis_checker.py now scans xilinx_root subdirectories:
  1. System PATH (shutil.which) — works on all platforms
  2. Legacy vitis_path/bin
  3. xilinx_root/Vitis/*/bin and Vivado/*/bin (all versions, newest first)
  4. Common install paths (Windows: C:/Xilinx, Linux: /data/xilinx, /opt/xilinx)

Multi-version: scans all version directories under Vitis/ and Vivado/,
picks the newest version for each tool.

Config: xilinx_path replaces vitis_path (backward compat kept)
This commit is contained in:
Jeremy Shen
2026-06-10 14:27:47 +08:00
parent 699ad82834
commit 93ab224d6d
7 changed files with 132 additions and 69 deletions
+73 -52
View File
@@ -52,86 +52,117 @@ TOOL_ALIASES: dict[str, list[str]] = {
}
def _find_executable(exec_name: str, vitis_path: str, system: str) -> str | None:
"""Find an executable by name using PATH and Vitis search paths.
def _find_executable(
exec_name: str,
vitis_path: str,
system: str,
xilinx_root: str = "",
) -> str | None:
"""Find an executable by name using PATH, Vitis, and Xilinx root.
Search order:
1. System PATH (shutil.which)
2. vitis_path/bin (legacy config)
3. xilinx_root/Vitis/*/bin and Vivado/*/bin (multi-version scan)
4. Common install paths
Args:
exec_name: Executable name (without extension).
vitis_path: Vitis/Vivado installation root directory.
vitis_path: Legacy Vitis installation directory.
system: Platform identifier (linux, windows, darwin).
xilinx_root: Xilinx root directory (e.g., /data/xilinx or C:/Xilinx).
Returns:
Absolute path to the executable, or None.
"""
exec_suffix = ".exe" if system == "windows" else ""
# 1. shutil.which() — most reliable cross-platform PATH resolution
# 1. System PATH
found = shutil.which(f"{exec_name}{exec_suffix}")
if found:
return found
# 2. Scan vitis_path/bin
# 2. Legacy vitis_path/bin
if vitis_path:
candidate = Path(vitis_path) / "bin" / f"{exec_name}{exec_suffix}"
if candidate.is_file():
return str(candidate)
# 3. Common Xilinx install paths
common_paths = [
f"/data/xilinx/Vitis/2023.2/bin/{exec_name}",
f"/data/xilinx/Vivado/2023.2/bin/{exec_name}",
f"/opt/xilinx/bin/{exec_name}",
os.path.expanduser(f"~/Xilinx/Vitis/2023.2/bin/{exec_name}"),
os.path.expanduser(f"~/Xilinx/Vivado/2023.2/bin/{exec_name}"),
]
for p in common_paths:
# 3. Scan xilinx_root for all versions (Vitis + Vivado)
if xilinx_root:
xr = Path(xilinx_root)
for product in ("Vitis", "Vivado"):
pdir = xr / product
if not pdir.is_dir():
continue
# Scan version directories, newest first
versions = sorted(
[d for d in pdir.iterdir() if d.is_dir()],
reverse=True,
)
for ver_dir in versions:
candidate = ver_dir / "bin" / f"{exec_name}{exec_suffix}"
if candidate.is_file():
return str(candidate)
# 4. Common install paths (cross-platform)
if system == "windows":
common = [
f"C:/Xilinx/Vitis/2023.2/bin/{exec_name}.exe",
f"C:/Xilinx/Vivado/2023.2/bin/{exec_name}.exe",
]
else:
common = [
f"/data/xilinx/Vitis/2023.2/bin/{exec_name}",
f"/data/xilinx/Vivado/2023.2/bin/{exec_name}",
f"/opt/xilinx/bin/{exec_name}",
os.path.expanduser(f"~/Xilinx/Vitis/2023.2/bin/{exec_name}"),
os.path.expanduser(f"~/Xilinx/Vivado/2023.2/bin/{exec_name}"),
]
for p in common:
if os.path.isfile(p):
return p
return None
def get_xsct_path(vitis_path: str = "") -> str | None:
def get_xsct_path(vitis_path: str = "", xilinx_root: str = "") -> str | None:
"""Find the xsct (Xilinx Software Command Line Tool) executable.
xsct is the primary JTAG scripting tool for Vivado/Vitis (2015.x+).
Args:
vitis_path: Optional Vitis installation directory.
vitis_path: Legacy Vitis install path.
xilinx_root: Xilinx root directory.
Returns:
Absolute path to xsct, or None.
"""
return _find_executable("xsct", vitis_path, platform.system().lower())
return _find_executable("xsct", vitis_path, platform.system().lower(), xilinx_root)
def get_program_flash_path(vitis_path: str = "") -> str | None:
"""Find the program_flash executable (QSPI flash programming).
program_flash is part of Vitis/SDK (2014.x+).
def get_program_flash_path(vitis_path: str = "", xilinx_root: str = "") -> str | None:
"""Find the program_flash executable.
Args:
vitis_path: Optional Vitis installation directory.
vitis_path: Legacy Vitis install path.
xilinx_root: Xilinx root directory.
Returns:
Absolute path to program_flash, or None.
"""
return _find_executable("program_flash", vitis_path, platform.system().lower())
return _find_executable("program_flash", vitis_path, platform.system().lower(), xilinx_root)
def get_bootgen_path(vitis_path: str = "") -> str | None:
"""Find the bootgen executable (BOOT.BIN generation).
bootgen creates boot images from BIF descriptions. It does NOT
program flash — use program_flash for that.
def get_bootgen_path(vitis_path: str = "", xilinx_root: str = "") -> str | None:
"""Find the bootgen executable.
Args:
vitis_path: Optional Vitis installation directory.
vitis_path: Legacy Vitis install path.
xilinx_root: Xilinx root directory.
Returns:
Absolute path to bootgen, or None.
"""
return _find_executable("bootgen", vitis_path, platform.system().lower())
return _find_executable("bootgen", vitis_path, platform.system().lower(), xilinx_root)
def _get_tool_version(tool_path: str, tool_name: str) -> str:
@@ -162,8 +193,6 @@ def _get_tool_version(tool_path: str, tool_name: str) -> str:
def check_vitis(config: Config) -> VitisCheckResult:
"""Check availability of all required Xilinx tools.
Scans PATH and Vitis paths for: xsct, program_flash, bootgen.
Args:
config: Application configuration.
@@ -171,19 +200,20 @@ def check_vitis(config: Config) -> VitisCheckResult:
VitisCheckResult with per-tool status.
"""
system = platform.system().lower()
vitis_path = config.vitis_path or ""
vitis_path = config.vitis_path if hasattr(config, 'vitis_path') else ""
xilinx_root = config.xilinx_path if hasattr(config, 'xilinx_path') else ""
tools: dict[str, ToolStatus] = {}
errors: list[str] = []
for tool_name, aliases in TOOL_ALIASES.items():
status = _check_tool(tool_name, aliases, vitis_path, system)
status = _check_tool(tool_name, aliases, vitis_path, system, xilinx_root)
tools[tool_name] = status
if not status.found:
errors.append(f"{tool_name}: {status.error or 'not found'}")
return VitisCheckResult(
system=system,
vitis_path=vitis_path,
vitis_path=xilinx_root or vitis_path,
tools=tools,
is_ready=len(errors) == 0,
errors=errors,
@@ -195,20 +225,11 @@ def _check_tool(
aliases: list[str],
vitis_path: str,
system: str,
xilinx_root: str = "",
) -> ToolStatus:
"""Check if a single tool is available.
Args:
tool_name: Canonical tool name.
aliases: Executable names to try.
vitis_path: Vitis installation path.
system: Platform identifier.
Returns:
ToolStatus with detection results.
"""
"""Check if a single tool is available."""
for alias in aliases:
found = _find_executable(alias, vitis_path, system)
found = _find_executable(alias, vitis_path, system, xilinx_root)
if found:
version = _get_tool_version(found, tool_name)
return ToolStatus(
@@ -223,9 +244,9 @@ def _check_tool(
name=tool_name,
found=False,
error=(
f"Could not locate {', '.join(aliases)} in PATH or {vitis_path}/bin"
if vitis_path
else f"Could not locate {', '.join(aliases)} in PATH"
f"Could not locate {', '.join(aliases)} in PATH"
+ (f" or {xilinx_root}" if xilinx_root else "")
+ (f" or {vitis_path}" if vitis_path else "")
),
)