proof-aware-crypto-tooling-.../provider/src/pacta_provider/discovery.py

82 lines
2.8 KiB
Python

from __future__ import annotations
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable
from pacta.lean import build_lean_env, detect_tools, resolve_lean_project_dir
SKIP_DIRS = {".git", ".venv", "__pycache__", "node_modules", "target", ".lake", ".pytest_cache"}
@dataclass(slots=True)
class ToolchainCandidate:
env_script: Path
lean_project_dir: Path | None
lean: str | None
lake: str | None
aeneas_home: str | None
def to_dict(self) -> dict[str, str | None]:
return {
"env_script": str(self.env_script),
"lean_project_dir": str(self.lean_project_dir) if self.lean_project_dir else None,
"lean": self.lean,
"lake": self.lake,
"aeneas_home": self.aeneas_home,
}
def discover_toolchains(roots: Iterable[str | Path] | None = None, max_depth: int = 6) -> list[ToolchainCandidate]:
home = Path.home()
search_roots = [home / "GitClone", home] if roots is None else [Path(root).expanduser() for root in roots]
scripts: list[Path] = []
for root in search_roots:
if root.exists():
scripts.extend(_find_env_scripts(root, max_depth=max_depth))
candidates: list[ToolchainCandidate] = []
seen: set[Path] = set()
for script in scripts:
if script in seen:
continue
seen.add(script)
env = build_lean_env("verification", env_script=script)
tools = detect_tools(env)
project = resolve_lean_project_dir("$AENEAS_HOME/backends/lean", env)
if project is None:
project = _nearby_lean_project(script)
candidates.append(
ToolchainCandidate(
env_script=script,
lean_project_dir=project,
lean=tools.lean,
lake=tools.lake,
aeneas_home=env.get("AENEAS_HOME"),
)
)
return candidates
def _find_env_scripts(root: Path, max_depth: int) -> list[Path]:
root = root.resolve()
found: list[Path] = []
for current, dirs, files in os.walk(root):
current_path = Path(current)
rel_depth = len(current_path.relative_to(root).parts)
if rel_depth >= max_depth:
dirs[:] = []
else:
dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
if "env.sh" in files and ("aeneas" in str(current_path).lower() or "lean" in str(current_path).lower()):
found.append(current_path / "env.sh")
return sorted(found)
def _nearby_lean_project(script: Path) -> Path | None:
for parent in [script.parent, *script.parents]:
for candidate in (parent / "aeneas" / "backends" / "lean", parent / "backends" / "lean"):
if (candidate / "lakefile.lean").exists() or (candidate / "lakefile.toml").exists():
return candidate
return None