fix(vitis): add xsct fallback for impact detection in Xilinx 2023.2

- vitis_checker: shutil.which() + xsct fallback for impact detection
- flash_programmer: _get_impact_path now checks xsct as fallback
- bitstream_programmer: _get_impact_path now checks xsct as fallback
- GUI: show '(via xsct)' alias note when xsct replaces impact
- ToolStatus: add 'alias' field for tracking which executable was found
This commit is contained in:
Jeremy Shen
2026-06-09 18:07:39 +08:00
parent 3a9bc5d9ed
commit 90c3dca8b8
4 changed files with 76 additions and 46 deletions
+14 -10
View File
@@ -33,6 +33,7 @@ ProgressCallback = Callable[[str, str], None] | None
def _get_impact_path(vitis_path: str) -> str | None:
"""Find the impact executable path.
Tries 'impact' first, then falls back to 'xsct' (Xilinx 2023.2+).
Uses shutil.which() for cross-platform compatibility,
then falls back to common Vitis paths.
@@ -40,18 +41,21 @@ def _get_impact_path(vitis_path: str) -> str | None:
vitis_path: Vitis installation path.
Returns:
Path to impact executable, or None if not found.
Path to impact or xsct executable, or None if not found.
"""
path = shutil.which("impact")
if path:
return path
# Try shutil.which for impact, then xsct as fallback
for tool in ("impact", "xsct"):
path = shutil.which(tool)
if path:
return path
if vitis_path:
candidate = Path(vitis_path) / "bin" / "impact"
if candidate.exists():
return str(candidate)
candidate = Path(vitis_path) / "bin" / "impact.exe"
if candidate.exists():
return str(candidate)
bin_dir = Path(vitis_path) / "bin"
if bin_dir.exists():
for tool in ("impact", "xsct"):
for ext in ("", ".exe"):
candidate = bin_dir / f"{tool}{ext}"
if candidate.exists():
return str(candidate)
common_paths = [
"/opt/xilinx/bin/impact",
os.path.expanduser("~/Xilinx/Vitis/bin/impact"),
+14 -11
View File
@@ -75,6 +75,7 @@ def _run_command(
def _get_impact_path(vitis_path: str) -> str | None:
"""Find the impact executable path.
Tries 'impact' first, then falls back to 'xsct' (Xilinx 2023.2+).
Uses shutil.which() for cross-platform compatibility,
then falls back to common Vitis paths.
@@ -82,21 +83,23 @@ def _get_impact_path(vitis_path: str) -> str | None:
vitis_path: Vitis installation path.
Returns:
Path to impact executable, or None if not found.
Path to impact or xsct executable, or None if not found.
"""
# Try shutil.which first (cross-platform)
path = shutil.which("impact")
if path:
return path
# Try shutil.which for impact, then xsct as fallback
for tool in ("impact", "xsct"):
path = shutil.which(tool)
if path:
return path
# Search in vitis_path/bin
if vitis_path:
candidate = Path(vitis_path) / "bin" / "impact"
if candidate.exists():
return str(candidate)
candidate = Path(vitis_path) / "bin" / "impact.exe"
if candidate.exists():
return str(candidate)
bin_dir = Path(vitis_path) / "bin"
if bin_dir.exists():
for tool in ("impact", "xsct"):
for ext in ("", ".exe"):
candidate = bin_dir / f"{tool}{ext}"
if candidate.exists():
return str(candidate)
# Common Vitis installation paths
common_paths = [
+2 -1
View File
@@ -487,7 +487,8 @@ class MainWindow(ctk.CTk):
for tool_name, tool_info in status_dict["tools"].items():
if tool_info["found"]:
self._log_message(f"{tool_name}: {tool_info['path']}")
alias_note = f" (via {tool_info['alias']})" if tool_info.get("alias") and tool_info["alias"] != tool_name else ""
self._log_message(f"{tool_name}: {tool_info['path']}{alias_note}")
else:
self._log_message(f"{tool_name}: {tool_info['error']}")
+46 -24
View File
@@ -8,8 +8,9 @@ from __future__ import annotations
import os
import platform
import shutil
import subprocess
from dataclasses import dataclass
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
@@ -25,6 +26,7 @@ class ToolStatus:
path: str = ""
version: str = ""
error: str = ""
alias: str = "" # Which alias was found (e.g. 'xsct' for 'impact')
@dataclass
@@ -55,10 +57,14 @@ def check_vitis(config: Config) -> VitisCheckResult:
tools: dict[str, ToolStatus] = {}
errors: list[str] = []
required_tools = ["bootgen", "impact"]
# Xilinx tool aliases: map tool names to their possible executables
tool_aliases: dict[str, list[str]] = {
"bootgen": ["bootgen"],
"impact": ["impact", "xsct"], # xsct can replace impact in newer versions
}
for tool_name in required_tools:
status = _check_tool(tool_name, vitis_path, system)
for tool_name, aliases in tool_aliases.items():
status = _check_tool(tool_name, aliases, vitis_path, system)
tools[tool_name] = status
if not status.found:
errors.append(f"{tool_name}: {status.error or 'not found'}")
@@ -73,46 +79,61 @@ def check_vitis(config: Config) -> VitisCheckResult:
def _check_tool(
tool_name: str, vitis_path: str, system: str
tool_name: str, aliases: list[str], vitis_path: str, system: str
) -> ToolStatus:
"""Check if a single tool is available.
Uses shutil.which() for reliable PATH resolution, then falls back
to scanning vitis_path/bin for each alias.
Args:
tool_name: Name of the tool to check.
tool_name: Canonical name of the tool (e.g. 'impact').
aliases: List of possible executable names (e.g. ['impact', 'xsct']).
vitis_path: Vitis installation path to search.
system: Platform identifier (linux, windows, darwin).
Returns:
ToolStatus with detection results.
"""
# Search in vitis_path/bin first, then system PATH
search_dirs: list[str] = []
if vitis_path:
bin_dir = Path(vitis_path) / "bin"
if bin_dir.exists():
search_dirs.append(str(bin_dir))
exec_suffix = ".exe" if system == "windows" else ""
# Add system PATH components
path_env = os.environ.get("PATH", "")
search_dirs.extend(path_env.split(os.pathsep))
exec_name = f"{tool_name}.exe" if system == "windows" else tool_name
for search_dir in search_dirs:
candidate = Path(search_dir) / exec_name
if candidate.exists() and candidate.is_file():
version = _get_tool_version(str(candidate), tool_name, system)
# 1. Try shutil.which() for each alias — most reliable PATH resolution
for alias in aliases:
found_path = shutil.which(f"{alias}{exec_suffix}")
if found_path:
version = _get_tool_version(found_path, tool_name, system)
return ToolStatus(
name=tool_name,
found=True,
path=str(candidate),
path=found_path,
version=version,
alias=alias,
)
# 2. Try vitis_path/bin for each alias
if vitis_path:
bin_dir = Path(vitis_path) / "bin"
if bin_dir.exists():
for alias in aliases:
candidate = bin_dir / f"{alias}{exec_suffix}"
if candidate.exists() and candidate.is_file():
version = _get_tool_version(str(candidate), tool_name, system)
return ToolStatus(
name=tool_name,
found=True,
path=str(candidate),
version=version,
alias=alias,
)
return ToolStatus(
name=tool_name,
found=False,
error=f"Could not locate {exec_name} in PATH or {vitis_path}/bin",
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"
),
)
@@ -163,6 +184,7 @@ def get_tool_status_dict(result: VitisCheckResult) -> dict[str, Any]:
"path": status.path,
"version": status.version,
"error": status.error,
"alias": status.alias,
}
for name, status in result.tools.items()
},