diff --git a/src/bitstream_programmer.py b/src/bitstream_programmer.py index e9e52b6..02b51e0 100644 --- a/src/bitstream_programmer.py +++ b/src/bitstream_programmer.py @@ -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"), diff --git a/src/flash_programmer.py b/src/flash_programmer.py index 713c57e..8584824 100644 --- a/src/flash_programmer.py +++ b/src/flash_programmer.py @@ -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 = [ diff --git a/src/gui/main_window.py b/src/gui/main_window.py index 03a8c26..ece51bf 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -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']}") diff --git a/src/vitis_checker.py b/src/vitis_checker.py index 8294453..5325e9a 100644 --- a/src/vitis_checker.py +++ b/src/vitis_checker.py @@ -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() },