Skip to content

Commit a7471b1

Browse files
committed
trial chisel 7 support runs
1 parent b15e4d8 commit a7471b1

3 files changed

Lines changed: 407 additions & 0 deletions

File tree

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import datetime as dt
5+
import json
6+
import os
7+
import re
8+
import subprocess
9+
import sys
10+
import time
11+
from pathlib import Path
12+
13+
14+
ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
15+
16+
17+
def strip_ansi(text):
18+
return ANSI_RE.sub("", text)
19+
20+
21+
def utc_now():
22+
return dt.datetime.now(dt.timezone.utc).isoformat().replace("+00:00", "Z")
23+
24+
25+
def read_build_versions(repo_dir):
26+
build_sbt = repo_dir / "build.sbt"
27+
versions = {}
28+
if not build_sbt.exists():
29+
return versions
30+
text = build_sbt.read_text(encoding="utf-8")
31+
for key in ("chisel6Version", "chisel7Version", "chiselTestVersion", "chisel3Version"):
32+
match = re.search(rf'val\s+{key}\s*=\s*"([^"]+)"', text)
33+
if match:
34+
versions[key] = match.group(1)
35+
return versions
36+
37+
38+
def sbt_base_command(repo_dir):
39+
return [
40+
"java",
41+
"-jar",
42+
str(repo_dir / "scripts" / "sbt-launch.jar"),
43+
f"-Dsbt.ivy.home={repo_dir / '.ivy2'}",
44+
f"-Dsbt.global.base={repo_dir / '.sbt'}",
45+
f"-Dsbt.boot.directory={repo_dir / '.sbt' / 'boot'}",
46+
"-Dsbt.color=false",
47+
"-Dsbt.supershell=false",
48+
"-Dsbt.server.forcestart=false",
49+
]
50+
51+
52+
def audit_env(repo_dir):
53+
env = os.environ.copy()
54+
env["USE_CHISEL7"] = "1"
55+
env.setdefault(
56+
"JAVA_TOOL_OPTIONS",
57+
f"-Xmx8G -Xss8M -Djava.io.tmpdir={repo_dir / '.java_tmp'}",
58+
)
59+
return env
60+
61+
62+
def run_sbt(repo_dir, sbt_command, log_path, timeout_seconds):
63+
command = sbt_base_command(repo_dir) + [sbt_command]
64+
started = time.monotonic()
65+
log_path.parent.mkdir(parents=True, exist_ok=True)
66+
with log_path.open("w", encoding="utf-8", errors="replace") as log_file:
67+
log_file.write(f"$ {' '.join(command)}\n\n")
68+
log_file.flush()
69+
try:
70+
proc = subprocess.run(
71+
command,
72+
cwd=repo_dir,
73+
env=audit_env(repo_dir),
74+
stdout=log_file,
75+
stderr=subprocess.STDOUT,
76+
text=True,
77+
timeout=timeout_seconds,
78+
check=False,
79+
)
80+
return proc.returncode, time.monotonic() - started, False
81+
except subprocess.TimeoutExpired:
82+
log_file.write(f"\nTimed out after {timeout_seconds} seconds\n")
83+
return 124, time.monotonic() - started, True
84+
85+
86+
def tail_lines(path, limit):
87+
if not path.exists():
88+
return []
89+
lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
90+
return [strip_ansi(line) for line in lines[-limit:]]
91+
92+
93+
def error_summary(path, limit):
94+
if not path.exists():
95+
return []
96+
selected = []
97+
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
98+
clean = strip_ansi(line)
99+
if "[error]" in clean or "Compilation failed" in clean or "not found:" in clean:
100+
selected.append(clean)
101+
if len(selected) >= limit:
102+
break
103+
return selected
104+
105+
106+
def discover_projects(repo_dir, log_dir, timeout_seconds):
107+
log_path = log_dir / "sbt-projects.log"
108+
returncode, _, timed_out = run_sbt(repo_dir, "projects", log_path, timeout_seconds)
109+
if returncode != 0 or timed_out:
110+
raise RuntimeError(f"sbt projects failed; see {log_path}")
111+
112+
projects = []
113+
collecting = False
114+
for raw in log_path.read_text(encoding="utf-8", errors="replace").splitlines():
115+
line = strip_ansi(raw)
116+
if line.startswith("[info] In file:"):
117+
collecting = True
118+
continue
119+
if not collecting:
120+
continue
121+
match = re.match(r"\[info\]\s+\*?\s+([A-Za-z0-9_.-]+)\s*$", line)
122+
if match:
123+
projects.append(match.group(1))
124+
return sorted(dict.fromkeys(projects))
125+
126+
127+
def project_result(project, status, returncode, duration, log_path, args):
128+
return {
129+
"project": project,
130+
"status": status,
131+
"returncode": returncode,
132+
"duration_seconds": round(duration, 3),
133+
"log_path": str(log_path),
134+
"errors": error_summary(log_path, args.max_error_lines),
135+
"log_tail": tail_lines(log_path, args.tail_lines) if status != "pass" else [],
136+
}
137+
138+
139+
def github_metadata():
140+
env = os.environ
141+
return {
142+
"repository": env.get("GITHUB_REPOSITORY"),
143+
"ref": env.get("GITHUB_REF"),
144+
"ref_name": env.get("GITHUB_REF_NAME"),
145+
"sha": env.get("GITHUB_SHA"),
146+
"workflow": env.get("GITHUB_WORKFLOW"),
147+
"run_id": env.get("GITHUB_RUN_ID"),
148+
"run_number": env.get("GITHUB_RUN_NUMBER"),
149+
"run_attempt": env.get("GITHUB_RUN_ATTEMPT"),
150+
"actor": env.get("GITHUB_ACTOR"),
151+
"server_url": env.get("GITHUB_SERVER_URL"),
152+
}
153+
154+
155+
def main():
156+
parser = argparse.ArgumentParser(description="Audit SBT projects against Chisel 7.")
157+
parser.add_argument("--repo-dir", default=".")
158+
parser.add_argument("--output", default="chisel7-support-results.json")
159+
parser.add_argument("--log-dir", default="chisel7-support-logs")
160+
parser.add_argument("--projects", nargs="*", default=None)
161+
parser.add_argument("--exclude", nargs="*", default=["chipyardRoot"])
162+
parser.add_argument("--project-timeout-minutes", type=int, default=45)
163+
parser.add_argument("--projects-timeout-minutes", type=int, default=10)
164+
parser.add_argument("--max-error-lines", type=int, default=40)
165+
parser.add_argument("--tail-lines", type=int, default=80)
166+
args = parser.parse_args()
167+
168+
repo_dir = Path(args.repo_dir).resolve()
169+
output_path = Path(args.output).resolve()
170+
log_dir = Path(args.log_dir).resolve()
171+
output_path.parent.mkdir(parents=True, exist_ok=True)
172+
log_dir.mkdir(parents=True, exist_ok=True)
173+
174+
projects = args.projects
175+
if not projects:
176+
print("Discovering SBT projects under USE_CHISEL7=1", flush=True)
177+
projects = discover_projects(repo_dir, log_dir, args.projects_timeout_minutes * 60)
178+
179+
excluded = set(args.exclude or [])
180+
projects = [project for project in projects if project not in excluded]
181+
182+
print(f"Auditing {len(projects)} SBT projects under USE_CHISEL7=1", flush=True)
183+
results = []
184+
for index, project in enumerate(projects, start=1):
185+
log_path = log_dir / f"{project}.log"
186+
print(f"::group::[{index}/{len(projects)}] {project}", flush=True)
187+
print(f"Running Chisel 7 compile check for {project}", flush=True)
188+
returncode, duration, timed_out = run_sbt(
189+
repo_dir,
190+
f";project {project}; compile",
191+
log_path,
192+
args.project_timeout_minutes * 60,
193+
)
194+
status = "timeout" if timed_out else "pass" if returncode == 0 else "fail"
195+
result = project_result(project, status, returncode, duration, log_path, args)
196+
results.append(result)
197+
print(f"{project}: {status} in {duration:.1f}s", flush=True)
198+
if status != "pass":
199+
for line in result["errors"][:10]:
200+
print(line, flush=True)
201+
print("::endgroup::", flush=True)
202+
203+
summary = {
204+
"total": len(results),
205+
"passed": sum(1 for item in results if item["status"] == "pass"),
206+
"failed": sum(1 for item in results if item["status"] == "fail"),
207+
"timed_out": sum(1 for item in results if item["status"] == "timeout"),
208+
}
209+
210+
document = {
211+
"schema_version": 1,
212+
"generated_at": utc_now(),
213+
"audit_type": "sbt_compile",
214+
"environment": {
215+
"use_chisel7": True,
216+
"versions": read_build_versions(repo_dir),
217+
"python": sys.version,
218+
},
219+
"github": github_metadata(),
220+
"summary": summary,
221+
"projects": results,
222+
}
223+
224+
output_path.write_text(json.dumps(document, indent=2, sort_keys=True) + "\n", encoding="utf-8")
225+
print(json.dumps(summary, sort_keys=True), flush=True)
226+
print(f"Wrote {output_path}", flush=True)
227+
return 0
228+
229+
230+
if __name__ == "__main__":
231+
sys.exit(main())
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import datetime as dt
5+
import json
6+
import os
7+
import sys
8+
9+
10+
def utc_now():
11+
return dt.datetime.now(dt.timezone.utc).isoformat().replace("+00:00", "Z")
12+
13+
14+
def main():
15+
parser = argparse.ArgumentParser(description="Publish Chisel 7 support audit results to MongoDB.")
16+
parser.add_argument("results_json")
17+
parser.add_argument("--database", default="main")
18+
parser.add_argument("--collection", default="chisel7_support")
19+
parser.add_argument("--mongo-env", default="MONGODB_SRV")
20+
args = parser.parse_args()
21+
22+
mongo_uri = os.environ.get(args.mongo_env)
23+
if not mongo_uri:
24+
print(f"Missing {args.mongo_env}; skipping MongoDB publish", file=sys.stderr)
25+
return 0
26+
27+
try:
28+
from pymongo import MongoClient
29+
except ImportError:
30+
print("pymongo is not installed; run `python3 -m pip install pymongo` first", file=sys.stderr)
31+
return 2
32+
33+
with open(args.results_json, encoding="utf-8") as results_file:
34+
document = json.load(results_file)
35+
36+
document["published_at"] = utc_now()
37+
38+
client = MongoClient(mongo_uri, serverSelectionTimeoutMS=15000)
39+
collection = client[args.database][args.collection]
40+
inserted = collection.insert_one(document)
41+
print(f"Inserted Chisel 7 support audit document: {inserted.inserted_id}")
42+
return 0
43+
44+
45+
if __name__ == "__main__":
46+
sys.exit(main())

0 commit comments

Comments
 (0)