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: def _get_impact_path(vitis_path: str) -> str | None:
"""Find the impact executable path. """Find the impact executable path.
Tries 'impact' first, then falls back to 'xsct' (Xilinx 2023.2+).
Uses shutil.which() for cross-platform compatibility, Uses shutil.which() for cross-platform compatibility,
then falls back to common Vitis paths. 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. vitis_path: Vitis installation path.
Returns: 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") # Try shutil.which for impact, then xsct as fallback
if path: for tool in ("impact", "xsct"):
return path path = shutil.which(tool)
if path:
return path
if vitis_path: if vitis_path:
candidate = Path(vitis_path) / "bin" / "impact" bin_dir = Path(vitis_path) / "bin"
if candidate.exists(): if bin_dir.exists():
return str(candidate) for tool in ("impact", "xsct"):
candidate = Path(vitis_path) / "bin" / "impact.exe" for ext in ("", ".exe"):
if candidate.exists(): candidate = bin_dir / f"{tool}{ext}"
return str(candidate) if candidate.exists():
return str(candidate)
common_paths = [ common_paths = [
"/opt/xilinx/bin/impact", "/opt/xilinx/bin/impact",
os.path.expanduser("~/Xilinx/Vitis/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: def _get_impact_path(vitis_path: str) -> str | None:
"""Find the impact executable path. """Find the impact executable path.
Tries 'impact' first, then falls back to 'xsct' (Xilinx 2023.2+).
Uses shutil.which() for cross-platform compatibility, Uses shutil.which() for cross-platform compatibility,
then falls back to common Vitis paths. 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. vitis_path: Vitis installation path.
Returns: 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) # Try shutil.which for impact, then xsct as fallback
path = shutil.which("impact") for tool in ("impact", "xsct"):
if path: path = shutil.which(tool)
return path if path:
return path
# Search in vitis_path/bin # Search in vitis_path/bin
if vitis_path: if vitis_path:
candidate = Path(vitis_path) / "bin" / "impact" bin_dir = Path(vitis_path) / "bin"
if candidate.exists(): if bin_dir.exists():
return str(candidate) for tool in ("impact", "xsct"):
candidate = Path(vitis_path) / "bin" / "impact.exe" for ext in ("", ".exe"):
if candidate.exists(): candidate = bin_dir / f"{tool}{ext}"
return str(candidate) if candidate.exists():
return str(candidate)
# Common Vitis installation paths # Common Vitis installation paths
common_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(): for tool_name, tool_info in status_dict["tools"].items():
if tool_info["found"]: 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: else:
self._log_message(f"{tool_name}: {tool_info['error']}") self._log_message(f"{tool_name}: {tool_info['error']}")
+46 -24
View File
@@ -8,8 +8,9 @@ from __future__ import annotations
import os import os
import platform import platform
import shutil
import subprocess import subprocess
from dataclasses import dataclass from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -25,6 +26,7 @@ class ToolStatus:
path: str = "" path: str = ""
version: str = "" version: str = ""
error: str = "" error: str = ""
alias: str = "" # Which alias was found (e.g. 'xsct' for 'impact')
@dataclass @dataclass
@@ -55,10 +57,14 @@ def check_vitis(config: Config) -> VitisCheckResult:
tools: dict[str, ToolStatus] = {} tools: dict[str, ToolStatus] = {}
errors: list[str] = [] 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: for tool_name, aliases in tool_aliases.items():
status = _check_tool(tool_name, vitis_path, system) status = _check_tool(tool_name, aliases, vitis_path, system)
tools[tool_name] = status tools[tool_name] = status
if not status.found: if not status.found:
errors.append(f"{tool_name}: {status.error or 'not found'}") errors.append(f"{tool_name}: {status.error or 'not found'}")
@@ -73,46 +79,61 @@ def check_vitis(config: Config) -> VitisCheckResult:
def _check_tool( def _check_tool(
tool_name: str, vitis_path: str, system: str tool_name: str, aliases: list[str], vitis_path: str, system: str
) -> ToolStatus: ) -> ToolStatus:
"""Check if a single tool is available. """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: 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. vitis_path: Vitis installation path to search.
system: Platform identifier (linux, windows, darwin). system: Platform identifier (linux, windows, darwin).
Returns: Returns:
ToolStatus with detection results. ToolStatus with detection results.
""" """
# Search in vitis_path/bin first, then system PATH exec_suffix = ".exe" if system == "windows" else ""
search_dirs: list[str] = []
if vitis_path:
bin_dir = Path(vitis_path) / "bin"
if bin_dir.exists():
search_dirs.append(str(bin_dir))
# Add system PATH components # 1. Try shutil.which() for each alias — most reliable PATH resolution
path_env = os.environ.get("PATH", "") for alias in aliases:
search_dirs.extend(path_env.split(os.pathsep)) found_path = shutil.which(f"{alias}{exec_suffix}")
if found_path:
exec_name = f"{tool_name}.exe" if system == "windows" else tool_name version = _get_tool_version(found_path, tool_name, system)
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)
return ToolStatus( return ToolStatus(
name=tool_name, name=tool_name,
found=True, found=True,
path=str(candidate), path=found_path,
version=version, 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( return ToolStatus(
name=tool_name, name=tool_name,
found=False, 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, "path": status.path,
"version": status.version, "version": status.version,
"error": status.error, "error": status.error,
"alias": status.alias,
} }
for name, status in result.tools.items() for name, status in result.tools.items()
}, },