Skip to content

Commit fcbaf7a

Browse files
Merge pull request #11 from chipfoundry/dev
Dev
2 parents 26a8490 + 5af8ca1 commit fcbaf7a

3 files changed

Lines changed: 99 additions & 144 deletions

File tree

chipfoundry_cli/main.py

Lines changed: 77 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -2762,56 +2762,33 @@ def maybe_abort_no_space(err, step_label):
27622762
if install_precheck:
27632763
step_num = 8 if not only_mode else ""
27642764
console.print(f"\n[bold]Step {step_num}:[/bold] Installing precheck...")
2765-
precheck_dir = Path.home() / 'mpw_precheck'
27662765

2767-
# Check if already installed
2768-
is_installed = precheck_dir.exists() and (precheck_dir / '.git').exists()
2769-
2770-
if is_installed and not overwrite:
2771-
console.print("[green]✓[/green] Precheck already installed")
2772-
elif dry_run:
2773-
if is_installed:
2774-
console.print("[dim]Would reinstall mpw_precheck [--overwrite][/dim]")
2775-
else:
2776-
console.print("[dim]Would install mpw_precheck[/dim]")
2766+
if dry_run:
2767+
console.print("[dim]Would install/upgrade cf-precheck Python package[/dim]")
27772768
else:
27782769
try:
2779-
if precheck_dir.exists() and overwrite:
2780-
console.print(f"[cyan]Removing existing {precheck_dir}...[/cyan]")
2781-
shutil.rmtree(precheck_dir)
2782-
2783-
if not precheck_dir.exists():
2784-
console.print("[cyan]Cloning mpw_precheck...[/cyan]")
2785-
subprocess.run(
2786-
['git', 'clone', '--depth=1', 'https://github.com/chipfoundry/mpw_precheck.git', str(precheck_dir)],
2787-
check=True,
2788-
capture_output=True,
2789-
text=True
2790-
)
2791-
console.print("[green]✓[/green] Precheck cloned successfully")
2792-
2793-
if shutil.which('docker') is None:
2794-
had_errors = True
2795-
console.print("[red]✗[/red] Docker not found. Install Docker Desktop to pull the precheck image.")
2796-
console.print("[dim]Precheck repo cloned, but Docker image was not pulled.[/dim]")
2797-
else:
2798-
console.print("[cyan]Pulling precheck Docker image...[/cyan]")
2799-
subprocess.run(
2800-
['docker', 'pull', 'chipfoundry/mpw_precheck:latest'],
2801-
check=True,
2802-
capture_output=True
2770+
console.print("[cyan]Installing cf-precheck...[/cyan]")
2771+
subprocess.run(
2772+
[sys.executable, '-m', 'pip', 'install', '--upgrade', '-q', 'cf-precheck'],
2773+
check=True,
2774+
capture_output=True,
2775+
text=True,
2776+
)
2777+
try:
2778+
result = subprocess.run(
2779+
[sys.executable, '-c', 'from cf_precheck import __version__; print(__version__)'],
2780+
capture_output=True, text=True,
28032781
)
2804-
console.print("[green]✓[/green] Precheck Docker image ready")
2805-
2782+
version = result.stdout.strip()
2783+
console.print(f"[green]✓[/green] cf-precheck v{version} installed")
2784+
except Exception:
2785+
console.print("[green]✓[/green] cf-precheck installed")
28062786
except subprocess.CalledProcessError as e:
28072787
maybe_abort_no_space(e, "Precheck install")
28082788
had_errors = True
2809-
console.print(f"[red]✗[/red] Failed to install precheck: {e}")
2789+
console.print(f"[red]✗[/red] Failed to install cf-precheck: {e}")
28102790
if e.stderr:
28112791
console.print(f"[dim]{e.stderr}[/dim]")
2812-
except OSError as e:
2813-
had_errors = True
2814-
console.print(f"[red]✗[/red] Failed to install precheck: {e}")
28152792

28162793
# Summary
28172794
console.print("\n" + "="*60)
@@ -3216,21 +3193,22 @@ def repo_update(project_root, repo_owner, repo_name, branch, dry_run):
32163193

32173194
@main.command('precheck')
32183195
@click.option('--project-root', type=click.Path(exists=True, file_okay=False), help='Path to the project directory (defaults to current directory)')
3219-
@click.option('--disable-lvs', is_flag=True, help='Disable LVS check and run specific checks only')
3196+
@click.option('--skip-checks', multiple=True, help='Checks to skip (can be specified multiple times)')
3197+
@click.option('--magic-drc', is_flag=True, help='Include Magic DRC check (optional, off by default)')
32203198
@click.option('--checks', multiple=True, help='Specific checks to run (can be specified multiple times)')
32213199
@click.option('--dry-run', is_flag=True, help='Show the command without running')
3222-
def precheck(project_root, disable_lvs, checks, dry_run):
3223-
"""Run mpw_precheck validation on the project.
3200+
def precheck(project_root, skip_checks, magic_drc, checks, dry_run):
3201+
"""Run precheck validation on the project.
32243202
3225-
This runs the MPW (Multi-Project Wafer) precheck tool to validate
3226-
your design before submission.
3203+
This runs the cf-precheck tool to validate your design before
3204+
submission.
32273205
32283206
Examples:
3229-
cf precheck # Run all checks
3230-
cf precheck --disable-lvs # Skip LVS, run specific checks
3231-
cf precheck --checks license --checks makefile # Run specific checks
3207+
cf precheck # Run all checks
3208+
cf precheck --skip-checks lvs # Skip LVS check
3209+
cf precheck --magic-drc # Include optional Magic DRC
3210+
cf precheck --checks topcell_check # Run specific checks only
32323211
"""
3233-
# If .cf/project.json exists in cwd, use it as default project_root
32343212
cwd_root, _ = get_project_json_from_cwd()
32353213
if not project_root and cwd_root:
32363214
project_root = cwd_root
@@ -3239,20 +3217,17 @@ def precheck(project_root, disable_lvs, checks, dry_run):
32393217

32403218
project_root_path = Path(project_root)
32413219

3242-
# Check if project is initialized (allow graceful return)
32433220
if not check_project_initialized(project_root_path, 'precheck', dry_run=dry_run, allow_graceful=True):
32443221
console.print(f"[red]✗[/red] Project not initialized. Please run 'cf init' first.")
32453222
console.print("[yellow]Dependencies are required before running precheck.[/yellow]")
32463223
return
32473224

32483225
project_json_path = project_root_path / '.cf' / 'project.json'
32493226

3250-
# Check project type - GPIO config not needed for openframe
32513227
with open(project_json_path, 'r') as f:
32523228
project_data = json.load(f)
32533229
project_type = project_data.get('project', {}).get('type', 'digital')
32543230

3255-
# Check if GPIO configuration exists (not needed for openframe)
32563231
if project_type != 'openframe':
32573232
gpio_config = get_gpio_config_from_project_json(str(project_json_path))
32583233
if not gpio_config or len(gpio_config) == 0:
@@ -3261,10 +3236,8 @@ def precheck(project_root, disable_lvs, checks, dry_run):
32613236
console.print("[cyan]Please run 'cf gpio-config' to configure GPIO settings first.[/cyan]")
32623237
raise click.Abort()
32633238

3264-
precheck_root = Path.home() / 'mpw_precheck'
32653239
pdk_root = project_root_path / 'dependencies' / 'pdks'
32663240

3267-
# Detect PDK from project.json
32683241
pdk = 'sky130A'
32693242
if project_json_path.exists():
32703243
try:
@@ -3274,124 +3247,98 @@ def precheck(project_root, disable_lvs, checks, dry_run):
32743247
except:
32753248
pass
32763249

3277-
# Check if precheck is installed
3278-
if not precheck_root.exists():
3279-
console.print(f"[red]✗[/red] mpw_precheck not found at {precheck_root}")
3280-
console.print("[yellow]Run 'cf setup --only-precheck' to install[/yellow]")
3281-
return
3282-
3283-
# Check if PDK exists
32843250
if not (pdk_root / pdk).exists():
32853251
console.print(f"[red]✗[/red] PDK not found at {pdk_root / pdk}")
32863252
console.print("[yellow]Run 'cf setup --only-pdk' to install[/yellow]")
32873253
return
32883254

3289-
# Check Docker availability
3290-
docker_available = shutil.which('docker') is not None
3291-
if not docker_available:
3255+
if shutil.which('docker') is None:
32923256
console.print("[red]✗[/red] Docker not found. Docker is required to run precheck.")
32933257
return
32943258

3295-
# Build the checks list
3296-
if checks:
3297-
# User specified custom checks
3298-
checks_list = list(checks)
3299-
elif disable_lvs:
3300-
# Default checks when LVS is disabled
3301-
checks_list = [
3302-
'license', 'makefile', 'default', 'documentation', 'consistency',
3303-
'gpio_defines', 'xor', 'magic_drc', 'klayout_feol', 'klayout_beol',
3304-
'klayout_offgrid', 'klayout_met_min_ca_density',
3305-
'klayout_pin_label_purposes_overlapping_drawing', 'klayout_zeroarea'
3306-
]
3307-
else:
3308-
# All checks (default behavior)
3309-
checks_list = []
3310-
3311-
# Display configuration
3312-
console.print("\n" + "="*60)
3313-
console.print("[bold cyan]MPW Precheck[/bold cyan]")
3314-
console.print(f"Project: [yellow]{project_root_path}[/yellow]")
3315-
console.print(f"PDK: [yellow]{pdk}[/yellow]")
3316-
if disable_lvs:
3317-
console.print("Mode: [yellow]LVS disabled[/yellow]")
3318-
if checks_list:
3319-
console.print(f"Checks: [yellow]{', '.join(checks_list)}[/yellow]")
3320-
else:
3321-
console.print("Checks: [yellow]All checks[/yellow]")
3322-
console.print("="*60 + "\n")
3323-
3324-
# Build Docker command
3325-
import getpass
3326-
import pwd
3327-
3328-
user_id = os.getuid()
3329-
group_id = os.getgid()
3330-
33313259
pdk_path = pdk_root / pdk
3332-
pdkpath = pdk_path # Same as PDK_PATH in the Makefile
3333-
ipm_dir = Path.home() / '.ipm'
33343260

3335-
# Create .ipm directory if it doesn't exist
3336-
if not ipm_dir.exists():
3337-
ipm_dir.mkdir(parents=True, exist_ok=True)
3261+
docker_image = 'chipfoundry/mpw_precheck:latest'
33383262

33393263
docker_cmd = [
33403264
'docker', 'run', '--rm',
3341-
'-v', f'{precheck_root}:{precheck_root}',
33423265
'-v', f'{project_root_path}:{project_root_path}',
33433266
'-v', f'{pdk_root}:{pdk_root}',
3344-
'-v', f'{ipm_dir}:{ipm_dir}',
3345-
'-e', f'INPUT_DIRECTORY={project_root_path}',
3346-
'-e', f'PDK_PATH={pdk_path}',
3347-
'-e', f'PDK_ROOT={pdk_root}',
3348-
'-e', f'PDKPATH={pdkpath}',
3349-
'-u', f'{user_id}:{group_id}',
3350-
'chipfoundry/mpw_precheck:latest',
3351-
'bash', '-c',
3267+
docker_image,
3268+
'cf-precheck',
3269+
'-i', str(project_root_path),
3270+
'-p', str(pdk_path),
3271+
'-c', '/opt/caravel',
33523272
]
33533273

3354-
# Build the precheck command
3355-
precheck_cmd = f'cd {precheck_root} ; python3 mpw_precheck.py --input_directory {project_root_path} --pdk_path {pdk_path}'
3274+
if magic_drc:
3275+
docker_cmd.append('--magic-drc')
3276+
3277+
if skip_checks:
3278+
docker_cmd.extend(['--skip-checks'] + list(skip_checks))
33563279

3357-
if checks_list:
3358-
precheck_cmd += ' ' + ' '.join(checks_list)
3280+
if checks:
3281+
docker_cmd.extend(list(checks))
33593282

3360-
docker_cmd.append(precheck_cmd)
3283+
checks_display = ', '.join(checks) if checks else 'All checks'
3284+
console.print("\n" + "="*60)
3285+
console.print("[bold cyan]CF Precheck[/bold cyan]")
3286+
console.print(f"Project: [yellow]{project_root_path}[/yellow]")
3287+
console.print(f"PDK: [yellow]{pdk}[/yellow]")
3288+
if skip_checks:
3289+
console.print(f"Skipping: [yellow]{', '.join(skip_checks)}[/yellow]")
3290+
if magic_drc:
3291+
console.print("Magic DRC: [yellow]enabled[/yellow]")
3292+
console.print(f"Checks: [yellow]{checks_display}[/yellow]")
3293+
console.print("="*60 + "\n")
33613294

33623295
if dry_run:
33633296
console.print("[bold yellow]Dry run - would execute:[/bold yellow]\n")
33643297
console.print("[dim]" + ' '.join(docker_cmd) + "[/dim]")
33653298
return
33663299

3367-
# Run precheck
3368-
console.print("[cyan]Running mpw_precheck...[/cyan]")
3300+
# Pull/update Docker image before running
3301+
console.print(f"[cyan]Checking for Docker image updates...[/cyan]")
3302+
try:
3303+
subprocess.run(
3304+
['docker', 'pull', docker_image],
3305+
check=True,
3306+
capture_output=True,
3307+
)
3308+
console.print(f"[green]✓[/green] Docker image up to date")
3309+
except subprocess.CalledProcessError:
3310+
# Image might already be available locally, warn but continue
3311+
result = subprocess.run(
3312+
['docker', 'image', 'inspect', docker_image],
3313+
capture_output=True,
3314+
)
3315+
if result.returncode != 0:
3316+
console.print(f"[red]✗[/red] Docker image '{docker_image}' not found. Run 'cf setup --only-precheck' or check your connection.")
3317+
return
3318+
console.print("[yellow]⚠[/yellow] Could not check for image updates (using cached image)")
3319+
3320+
console.print("[cyan]Running cf-precheck...[/cyan]\n")
33693321

33703322
try:
3371-
# Use Popen for better signal handling
33723323
process = subprocess.Popen(
33733324
docker_cmd,
3374-
cwd=str(precheck_root),
33753325
preexec_fn=os.setsid if os.name != 'nt' else None
33763326
)
33773327

3378-
# Wait for process to complete
33793328
returncode = process.wait()
33803329

3381-
console.print("") # Add newline
3330+
console.print("")
33823331
if returncode == 0:
33833332
console.print("[green]✓[/green] Precheck passed!")
3384-
elif returncode == -2 or returncode == 130: # SIGINT
3333+
elif returncode == -2 or returncode == 130:
33853334
console.print("[yellow]⚠[/yellow] Precheck interrupted by user")
33863335
sys.exit(130)
33873336
else:
3388-
console.print(f"[red]✗[/red] Precheck failed with exit code {returncode}")
3389-
console.print(f"[yellow]Check the output above for details[/yellow]")
3337+
console.print(f"[red]✗[/red] Precheck failed (exit code {returncode})")
33903338
sys.exit(returncode)
33913339

33923340
except KeyboardInterrupt:
33933341
console.print("\n[yellow]⚠[/yellow] Precheck interrupted by user")
3394-
# Try to stop the Docker container gracefully
33953342
try:
33963343
if os.name != 'nt':
33973344
os.killpg(os.getpgid(process.pid), signal.SIGTERM)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "chipfoundry-cli"
3-
version = "2.1.1"
3+
version = "2.2.0"
44
description = "CLI tool to automate ChipFoundry project submission to SFTP server"
55
authors = ["ChipFoundry <marwan.abbas@chipfoundry.io>"]
66
readme = "README.md"

tests/test_precheck_command.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ def test_precheck_help(self):
2929
result = runner.invoke(main, ['precheck', '--help'])
3030

3131
assert result.exit_code == 0
32-
assert 'Run mpw_precheck validation' in result.output
32+
assert 'Run precheck validation' in result.output
3333
assert '--project-root' in result.output
34-
assert '--disable-lvs' in result.output
34+
assert '--skip-checks' in result.output
35+
assert '--magic-drc' in result.output
3536
assert '--checks' in result.output
3637
assert '--dry-run' in result.output
3738

@@ -44,24 +45,20 @@ def test_precheck_dry_run(self, temp_project_dir):
4445
'--dry-run'
4546
])
4647

47-
# Command returns 0 even on error, just prints error message
4848
assert result.exit_code == 0
49-
# May mention precheck, pdk, or setup in error message
5049
assert any(keyword in result.output.lower() for keyword in ['precheck', 'pdk', 'setup', 'dry'])
5150

52-
def test_precheck_disable_lvs(self, temp_project_dir):
53-
"""Test precheck command with --disable-lvs flag."""
51+
def test_precheck_skip_checks(self, temp_project_dir):
52+
"""Test precheck command with --skip-checks flag."""
5453
runner = CliRunner()
5554
result = runner.invoke(main, [
5655
'precheck',
5756
'--project-root', temp_project_dir,
58-
'--disable-lvs',
57+
'--skip-checks', 'lvs',
5958
'--dry-run'
6059
])
6160

62-
# Command returns 0 even on error, just prints error message
6361
assert result.exit_code == 0
64-
# May mention precheck, pdk, or setup in error message
6562
assert any(keyword in result.output.lower() for keyword in ['precheck', 'pdk', 'setup', 'dry'])
6663

6764
def test_precheck_with_checks(self, temp_project_dir):
@@ -70,14 +67,25 @@ def test_precheck_with_checks(self, temp_project_dir):
7067
result = runner.invoke(main, [
7168
'precheck',
7269
'--project-root', temp_project_dir,
73-
'--checks', 'license',
74-
'--checks', 'makefile',
70+
'--checks', 'topcell_check',
71+
'--checks', 'gpio_defines',
72+
'--dry-run'
73+
])
74+
75+
assert result.exit_code == 0
76+
assert any(keyword in result.output.lower() for keyword in ['precheck', 'pdk', 'setup', 'dry'])
77+
78+
def test_precheck_magic_drc(self, temp_project_dir):
79+
"""Test precheck command with --magic-drc flag."""
80+
runner = CliRunner()
81+
result = runner.invoke(main, [
82+
'precheck',
83+
'--project-root', temp_project_dir,
84+
'--magic-drc',
7585
'--dry-run'
7686
])
7787

78-
# Command returns 0 even on error, just prints error message
7988
assert result.exit_code == 0
80-
# May mention precheck, pdk, or setup in error message
8189
assert any(keyword in result.output.lower() for keyword in ['precheck', 'pdk', 'setup', 'dry'])
8290

8391

0 commit comments

Comments
 (0)