Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions chipcompiler/tools/yosys/utility.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python
import json
import os
import shutil
import subprocess
Expand All @@ -12,6 +13,27 @@ def _sanitize_loader_env(env: dict[str, str]) -> dict[str, str]:
return env


_MANIFEST_PATH = Path.home() / ".ecos" / "tools" / "manifest.json"


def _resolve_from_manifest(tool_name: str) -> tuple[list[str], Path | None]:
"""Check ~/.ecos/tools/manifest.json for a plugin-managed tool."""
if not _MANIFEST_PATH.exists():
return [], None
try:
manifest = json.loads(_MANIFEST_PATH.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return [], None
entry = manifest.get("installed", {}).get(tool_name)
if not entry:
return [], None
tool_dir = Path(entry["path"])
binary = tool_dir / "bin" / ("yosys.exe" if os.name == "nt" else tool_name)
if binary.exists():
return [str(binary)], tool_dir
return [], None


def _build_oss_cad_env(oss_path: Path, base_env: dict[str, str] | None = None) -> dict[str, str]:
"""Build subprocess environment variables for OSS CAD Suite."""
# TODO: Useless in nix build, consider remove this?
Expand Down Expand Up @@ -46,17 +68,24 @@ def _resolve_oss_yosys_paths() -> tuple[str, Path | None, Path | None]:

def _resolve_yosys_command() -> tuple[list[str], Path | None]:
"""
Resolve yosys executable from bundled runtime first, then system PATH.
Resolve yosys executable: manifest first, then bundled runtime, then system PATH.

Returns:
(command, oss_path):
- command: list containing executable command or empty list if unavailable
- oss_path: OSS CAD root path if bundled yosys is selected, else None
- oss_path: tool root path if resolved, else None
"""
# 1. Check manifest (plugin-managed tools)
cmd, path = _resolve_from_manifest("yosys")
if cmd:
return cmd, path

# 2. Check CHIPCOMPILER_OSS_CAD_DIR (bundled/Nix path)
_, oss_path, yosys_bin = _resolve_oss_yosys_paths()
if oss_path is not None and yosys_bin is not None and yosys_bin.exists():
return [str(yosys_bin)], oss_path

# 3. System PATH
if shutil.which("yosys"):
return ["yosys"], None

Expand Down
1 change: 1 addition & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
collect_ignore_glob = ["bazel-*"]
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ lint.select = [
]
extend-include = ["*.spec"]

[tool.pytest.ini_options]
testpaths = ["test"]
norecursedirs = ["bazel-bin", "bazel-out", "bazel-ecc", "bazel-testlogs", ".venv", "dist"]

[tool.ty]
environment.python-version = "3.11"
src.include = [ "chipcompiler/**/*.py", "tests/**/*.py" ]
134 changes: 134 additions & 0 deletions test/test_manifest_resolution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import json
import os
from pathlib import Path
from unittest.mock import patch

import pytest


def test_resolve_from_manifest_found(tmp_path: Path) -> None:
"""When manifest has yosys entry and binary exists, resolve it."""
from chipcompiler.tools.yosys.utility import _resolve_from_manifest

# Set up fake tool installation
tool_dir = tmp_path / "yosys" / "0.61"
bin_dir = tool_dir / "bin"
bin_dir.mkdir(parents=True)
yosys_bin = bin_dir / "yosys"
yosys_bin.write_text("#!/bin/sh\necho yosys")
yosys_bin.chmod(0o755)

manifest = {
"schema_version": 1,
"installed": {
"yosys": {
"version": "0.61",
"path": str(tool_dir),
"sha256": "abc123",
}
},
}
manifest_path = tmp_path / "manifest.json"
manifest_path.write_text(json.dumps(manifest))

with patch(
"chipcompiler.tools.yosys.utility._MANIFEST_PATH",
manifest_path,
):
cmd, tool_path = _resolve_from_manifest("yosys")
assert cmd == [str(yosys_bin)]
assert tool_path == tool_dir


def test_resolve_from_manifest_no_file(tmp_path: Path) -> None:
"""When manifest doesn't exist, return empty."""
from chipcompiler.tools.yosys.utility import _resolve_from_manifest

with patch(
"chipcompiler.tools.yosys.utility._MANIFEST_PATH",
tmp_path / "nonexistent.json",
):
cmd, tool_path = _resolve_from_manifest("yosys")
assert cmd == []
assert tool_path is None


def test_resolve_from_manifest_tool_not_installed(tmp_path: Path) -> None:
"""When manifest exists but tool not in it, return empty."""
from chipcompiler.tools.yosys.utility import _resolve_from_manifest

manifest = {"schema_version": 1, "installed": {}}
manifest_path = tmp_path / "manifest.json"
manifest_path.write_text(json.dumps(manifest))

with patch(
"chipcompiler.tools.yosys.utility._MANIFEST_PATH",
manifest_path,
):
cmd, tool_path = _resolve_from_manifest("yosys")
assert cmd == []
assert tool_path is None


def test_resolve_from_manifest_binary_missing(tmp_path: Path) -> None:
"""When manifest has entry but binary doesn't exist, return empty."""
from chipcompiler.tools.yosys.utility import _resolve_from_manifest

tool_dir = tmp_path / "yosys" / "0.61"
tool_dir.mkdir(parents=True)
# Don't create the binary

manifest = {
"schema_version": 1,
"installed": {
"yosys": {
"version": "0.61",
"path": str(tool_dir),
"sha256": "abc123",
}
},
}
manifest_path = tmp_path / "manifest.json"
manifest_path.write_text(json.dumps(manifest))

with patch(
"chipcompiler.tools.yosys.utility._MANIFEST_PATH",
manifest_path,
):
cmd, tool_path = _resolve_from_manifest("yosys")
assert cmd == []
assert tool_path is None


def test_resolve_yosys_command_checks_manifest_first(tmp_path: Path) -> None:
"""_resolve_yosys_command should check manifest before env var and PATH."""
from chipcompiler.tools.yosys.utility import _resolve_yosys_command

# Set up fake manifest tool
tool_dir = tmp_path / "yosys" / "0.61"
bin_dir = tool_dir / "bin"
bin_dir.mkdir(parents=True)
yosys_bin = bin_dir / "yosys"
yosys_bin.write_text("#!/bin/sh\necho yosys")
yosys_bin.chmod(0o755)

manifest = {
"schema_version": 1,
"installed": {
"yosys": {
"version": "0.61",
"path": str(tool_dir),
"sha256": "abc123",
}
},
}
manifest_path = tmp_path / "manifest.json"
manifest_path.write_text(json.dumps(manifest))

with patch(
"chipcompiler.tools.yosys.utility._MANIFEST_PATH",
manifest_path,
):
cmd, oss_path = _resolve_yosys_command()
assert cmd == [str(yosys_bin)]
assert oss_path == tool_dir