Skip to content

Commit 63a8598

Browse files
authored
fix(precheck): pass check names before --skip-checks for remote runs
1 parent 82fe79d commit 63a8598

6 files changed

Lines changed: 554 additions & 7 deletions

File tree

chipfoundry_cli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
"""ChipFoundry CLI package: Automate project submission to SFTP."""
2-
__version__ = "0.1.0"
2+
__version__ = "2.3.2"

chipfoundry_cli/main.py

Lines changed: 232 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import click
22
import getpass
3+
from chipfoundry_cli.remote_precheck_git import RemotePrecheckGitError, verify_remote_precheck_repo
34
from chipfoundry_cli.utils import (
45
collect_project_files, ensure_cf_directory, update_or_create_project_json,
56
sftp_connect, upload_with_progress, sftp_ensure_dirs, sftp_download_recursive,
@@ -1653,6 +1654,13 @@ def pull(project_name, output_dir, sftp_host, sftp_username, sftp_key):
16531654
"CHANGES_REQUESTED": "Changes requested by the review team. See notes above.",
16541655
}
16551656

1657+
REMOTE_PRECHECK_STATUS_COLORS = {
1658+
"queued": "yellow",
1659+
"running": "cyan bold",
1660+
"completed": "green",
1661+
"failed": "red",
1662+
}
1663+
16561664

16571665
def _show_platform_status(project_root: str):
16581666
"""Show the platform pipeline panel if the project is linked. Returns True if shown."""
@@ -1695,6 +1703,28 @@ def _show_platform_status(project_root: str):
16951703
lines.append(f"[bold]Tapeout:[/bold] [{tc}]{tl}[/{tc}]")
16961704
if project.get('gds_hash'):
16971705
lines.append(f"[bold]GDS Hash:[/bold] {project['gds_hash'][:16]}...")
1706+
rj = project.get("latest_remote_precheck_job")
1707+
if isinstance(rj, dict) and rj.get("status"):
1708+
jst = str(rj.get("status", ""))
1709+
jc = REMOTE_PRECHECK_STATUS_COLORS.get(jst, "white")
1710+
lines.append(f"[bold]Remote precheck:[/bold] [{jc}]{jst}[/{jc}]")
1711+
ref = rj.get("git_ref")
1712+
if ref:
1713+
lines.append(f"[dim] git ref: {ref}[/dim]")
1714+
created = rj.get("created_at")
1715+
if created and isinstance(created, str):
1716+
lines.append(f"[dim] started: {created[:19]}[/dim]")
1717+
if jst in ("completed", "failed"):
1718+
done = rj.get("completed_at")
1719+
if done and isinstance(done, str):
1720+
lines.append(f"[dim] finished: {done[:19]}[/dim]")
1721+
if jst == "failed" and rj.get("error_message"):
1722+
err = str(rj["error_message"])
1723+
if len(err) > 240:
1724+
err = err[:237] + "..."
1725+
lines.append(f"[red] {err}[/red]")
1726+
if jst == "completed" and rj.get("github_pr_url"):
1727+
lines.append(f"[green] PR:[/green] {rj['github_pr_url']}")
16981728
if project.get('updated_at'):
16991729
lines.append(f"[bold]Updated:[/bold] {project['updated_at'][:10]}")
17001730
if project.get('admin_review_notes'):
@@ -3248,7 +3278,21 @@ def _upload_precheck_results(project_json_path: Path):
32483278
@click.option('--magic-drc', is_flag=True, help='Include Magic DRC check (optional, off by default)')
32493279
@click.option('--checks', multiple=True, help='Specific checks to run (can be specified multiple times)')
32503280
@click.option('--dry-run', is_flag=True, help='Show the command without running')
3251-
def precheck(project_root, skip_checks, magic_drc, checks, dry_run):
3281+
@click.option('--remote', is_flag=True, help='Queue precheck on the chipIgnite platform (requires cf login + linked project)')
3282+
@click.option(
3283+
'--poll',
3284+
is_flag=True,
3285+
help='With --remote: poll until the job finishes and print progress (5s interval).',
3286+
)
3287+
@click.option('--git-ref', default='main', show_default=True, help='Git branch or tag for remote precheck')
3288+
@click.option(
3289+
'--wait-timeout',
3290+
type=int,
3291+
default=7200,
3292+
show_default=True,
3293+
help='With --remote --poll: max seconds to wait (0 = no limit). Ignored without --poll.',
3294+
)
3295+
def precheck(project_root, skip_checks, magic_drc, checks, dry_run, remote, poll, git_ref, wait_timeout):
32523296
"""Run precheck validation on the project.
32533297
32543298
This runs the cf-precheck tool to validate your design before
@@ -3259,6 +3303,13 @@ def precheck(project_root, skip_checks, magic_drc, checks, dry_run):
32593303
cf precheck --skip-checks lvs # Skip LVS check
32603304
cf precheck --magic-drc # Include optional Magic DRC
32613305
cf precheck --checks topcell_check # Run specific checks only
3306+
cf precheck --remote # Queue on platform; exit when accepted
3307+
cf precheck --remote --poll # Wait and stream progress
3308+
cf precheck --remote --poll --wait-timeout 0 # Poll until done (no time limit)
3309+
3310+
Remote precheck requires your local HEAD to match origin for --git-ref, and precheck
3311+
inputs (wrapper GDS, verilog/rtl/user_defines.v when the GPIO check runs, and tracked
3312+
.cf/project.json) to match that commit.
32623313
"""
32633314
cwd_root, _ = get_project_json_from_cwd()
32643315
if not project_root and cwd_root:
@@ -3274,6 +3325,180 @@ def precheck(project_root, skip_checks, magic_drc, checks, dry_run):
32743325
return
32753326

32763327
project_json_path = project_root_path / '.cf' / 'project.json'
3328+
3329+
if poll and not remote:
3330+
console.print("[red]✗[/red] --poll requires --remote.")
3331+
raise SystemExit(1)
3332+
3333+
if remote:
3334+
import time
3335+
from urllib.parse import urlencode
3336+
3337+
import httpx as httpx_remote
3338+
platform_id = _load_project_platform_id(str(project_root_path))
3339+
if not platform_id:
3340+
console.print(
3341+
"[red]✗[/red] Link this repo to a platform project (set platform_project_id via [bold]cf link[/bold])."
3342+
)
3343+
raise SystemExit(1)
3344+
try:
3345+
verify_remote_precheck_repo(
3346+
project_root_path,
3347+
git_ref,
3348+
checks=tuple(checks),
3349+
skip_checks=tuple(skip_checks),
3350+
)
3351+
except RemotePrecheckGitError as e:
3352+
console.print(f"[red]✗[/red] {e}")
3353+
raise SystemExit(1)
3354+
remote_params = [("git_ref", git_ref)]
3355+
# Single checks= / skip_checks= value so proxies do not drop duplicate query keys.
3356+
if checks:
3357+
remote_params.append(("checks", ",".join(checks)))
3358+
if skip_checks:
3359+
remote_params.append(("skip_checks", ",".join(skip_checks)))
3360+
if magic_drc:
3361+
remote_params.append(("magic_drc", "true"))
3362+
if dry_run:
3363+
console.print(
3364+
f"[cyan]Would POST[/cyan] /projects/{platform_id}/precheck-jobs?"
3365+
+ urlencode(remote_params)
3366+
)
3367+
return
3368+
if poll and wait_timeout < 0:
3369+
console.print(
3370+
"[red]✗[/red] --wait-timeout must be >= 0 (0 means no limit while polling)."
3371+
)
3372+
raise SystemExit(1)
3373+
config = load_user_config()
3374+
api_key = config.get('api_key')
3375+
if not api_key:
3376+
console.print("[yellow]Not logged in.[/yellow] Run [bold]cf login[/bold] first.")
3377+
raise SystemExit(1)
3378+
api_url = _get_api_url()
3379+
client = httpx_remote.Client(
3380+
base_url=f"{api_url}/api/v1",
3381+
headers={'Authorization': f'Bearer {api_key}'},
3382+
timeout=120.0,
3383+
)
3384+
try:
3385+
resp = client.post(
3386+
f"/projects/{platform_id}/precheck-jobs",
3387+
params=remote_params,
3388+
)
3389+
if resp.status_code == 401:
3390+
console.print("[red]✗[/red] API key is invalid or expired. Run [bold]cf login[/bold].")
3391+
raise SystemExit(1)
3392+
if not resp.is_success:
3393+
try:
3394+
detail = resp.json().get("detail", resp.text)
3395+
except Exception:
3396+
detail = resp.text
3397+
console.print(f"[red]✗[/red] {detail}")
3398+
raise SystemExit(1)
3399+
job = resp.json()
3400+
jid = job["id"]
3401+
st0 = job.get("status") or "unknown"
3402+
if st0 == "failed":
3403+
console.print(f"[cyan]Remote precheck[/cyan] job_id={jid} status={st0}")
3404+
elif st0 == "running":
3405+
console.print(f"[cyan]Remote precheck started[/cyan] job_id={jid} status={st0}")
3406+
else:
3407+
console.print(f"[cyan]Queued remote precheck[/cyan] job_id={jid} status={st0}")
3408+
if job.get("status") == "failed" and job.get("error_message"):
3409+
console.print(f"[red]✗[/red] {job['error_message']}")
3410+
raise SystemExit(1)
3411+
if job.get("status") == "completed":
3412+
console.print("[green]✓[/green] Remote precheck completed")
3413+
if job.get("github_pr_url"):
3414+
console.print(f" Pull request: {job['github_pr_url']}")
3415+
return
3416+
if not poll:
3417+
console.print(
3418+
"[dim]Not waiting: use [bold]cf precheck --remote --poll[/bold] to stream progress "
3419+
"([bold]--wait-timeout 0[/bold] = no time limit while polling).[/dim]"
3420+
)
3421+
return
3422+
deadline = None if wait_timeout == 0 else time.monotonic() + wait_timeout
3423+
if wait_timeout == 0:
3424+
console.print("[dim]Polling until the job completes (no timeout).[/dim]")
3425+
else:
3426+
console.print(
3427+
f"[dim]Polling every 5s; stops after {wait_timeout}s if still queued or running. "
3428+
f"Use [bold]--wait-timeout 0[/bold] for no limit.[/dim]"
3429+
)
3430+
last_status_seen = st0
3431+
terminal = None
3432+
github_pr_url = None
3433+
fail_message = None
3434+
progress_emitted = 0
3435+
console.print("[dim]Worker log batches appear below as the platform receives them (5s poll).[/dim]")
3436+
while True:
3437+
if deadline is not None and time.monotonic() > deadline:
3438+
console.print(
3439+
"[yellow]⚠[/yellow] Timed out waiting for remote precheck (job still queued or running)."
3440+
)
3441+
console.print(
3442+
f"[dim]job_id={jid} — open the project in the portal or run [bold]cf status[/bold].[/dim]"
3443+
)
3444+
console.print(
3445+
"[dim]Cancel a stuck run in the portal, or retry with e.g. "
3446+
"[bold]cf precheck --remote --poll --wait-timeout 14400[/bold].[/dim]"
3447+
)
3448+
raise SystemExit(1)
3449+
time.sleep(5)
3450+
r2 = client.get(f"/projects/{platform_id}/precheck-jobs/{jid}")
3451+
if r2.status_code == 401:
3452+
console.print("[red]✗[/red] API key is invalid or expired.")
3453+
raise SystemExit(1)
3454+
r2.raise_for_status()
3455+
j2 = r2.json()
3456+
st = j2.get("status")
3457+
prog = j2.get("progress")
3458+
if isinstance(prog, list) and len(prog) > progress_emitted:
3459+
for row in prog[progress_emitted:]:
3460+
if not isinstance(row, dict):
3461+
continue
3462+
msg = row.get("message")
3463+
if msg:
3464+
det = row.get("details")
3465+
if (
3466+
isinstance(det, dict)
3467+
and det.get("event") == "check_done"
3468+
):
3469+
console.print(Text(str(msg), style="bold"))
3470+
else:
3471+
console.print(Text(str(msg), style="dim"))
3472+
progress_emitted = len(prog)
3473+
if st == "completed":
3474+
terminal = "completed"
3475+
github_pr_url = j2.get("github_pr_url")
3476+
break
3477+
if st == "failed":
3478+
terminal = "failed"
3479+
fail_message = j2.get("error_message") or "unknown error"
3480+
break
3481+
if st != last_status_seen:
3482+
console.print(
3483+
f"[dim]… job status[/dim] [cyan]{st or 'unknown'}[/cyan]"
3484+
)
3485+
last_status_seen = st
3486+
3487+
if terminal == "completed":
3488+
console.print("[green]✓[/green] Remote precheck completed")
3489+
if github_pr_url:
3490+
console.print(f" Pull request: {github_pr_url}")
3491+
elif terminal == "failed":
3492+
console.print(f"[red]✗[/red] Remote precheck failed: {fail_message}")
3493+
raise SystemExit(1)
3494+
except SystemExit:
3495+
raise
3496+
except Exception as e:
3497+
console.print(f"[red]✗[/red] Remote precheck request failed: {e}")
3498+
raise SystemExit(1)
3499+
finally:
3500+
client.close()
3501+
return
32773502

32783503
with open(project_json_path, 'r') as f:
32793504
project_data = json.load(f)
@@ -3319,12 +3544,14 @@ def precheck(project_root, skip_checks, magic_drc, checks, dry_run):
33193544

33203545
if magic_drc:
33213546
precheck_args.append('--magic-drc')
3322-
3323-
if skip_checks:
3324-
precheck_args.extend(['--skip-checks'] + list(skip_checks))
3325-
3547+
3548+
# Positional check names before --skip-checks (matches cf-precheck argparse; see
3549+
# precheck-runner _cf_precheck_shell_cmd).
33263550
if checks:
33273551
precheck_args.extend(list(checks))
3552+
3553+
if skip_checks:
3554+
precheck_args.extend(['--skip-checks'] + list(skip_checks))
33283555

33293556
inner_cmd = 'pip3 install --upgrade -q --root-user-action=ignore cf-precheck 2>/dev/null && exec cf-precheck ' + ' '.join(precheck_args)
33303557

0 commit comments

Comments
 (0)