chipfoundry-cli 2.4.9__tar.gz → 2.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: chipfoundry-cli
3
- Version: 2.4.9
3
+ Version: 2.5.0
4
4
  Summary: CLI tool to automate ChipFoundry project submission to SFTP server
5
5
  Home-page: https://chipfoundry.io
6
6
  License: Apache-2.0
@@ -427,13 +427,18 @@ cf harden [MACRO] [OPTIONS]
427
427
 
428
428
  **Key Options:**
429
429
  - `--list` or no argument: List all available macros
430
+ - `--list-from-steps`: List valid LibreLane step names for `--from` for a specific macro
430
431
  - `MACRO`: Name of macro to harden (e.g., `user_proj_example`, `user_project_wrapper`)
431
432
  - `--project-root`: Specify project directory
432
- - `--tag`: Custom run tag (defaults to timestamp)
433
+ - `--tag`: Run tag. Without `--from`, existing tag is overwritten; with `--from`, resumes that tag
434
+ - `--from`: Start hardening from a specific LibreLane step (e.g., `OpenROAD.DetailedRouting`)
435
+ - `--tag` + `--from`: Resume from that step using the existing run state under the specified tag
436
+ - `--from` without `--tag`: Resume from that step using the latest existing run tag for the macro
437
+ - `--open-in-openroad`: Open an existing run in OpenROAD GUI (uses latest run if `--tag` omitted)
438
+ - `--open-in-klayout`: Open an existing run in KLayout GUI (uses latest run if `--tag` omitted)
433
439
  - `--pdk`: PDK to use (default: sky130A)
434
440
  - `--use-nix`: Force use of Nix (fails if Nix not available)
435
441
  - `--use-docker`: Force use of Docker (fails if Docker not available)
436
- - `--dry-run`: Show configuration without running
437
442
 
438
443
  **Examples:**
439
444
  ```bash
@@ -446,8 +451,22 @@ cf harden user_proj_example
446
451
  # Harden with custom tag and PDK
447
452
  cf harden user_proj_example --tag my_run --pdk sky130B
448
453
 
449
- # Preview hardening configuration
450
- cf harden user_proj_example --dry-run
454
+ # Resume flow from a specific LibreLane step
455
+ cf harden user_proj_example --tag test2 --from OpenROAD.DetailedRouting
456
+
457
+ # Resume from latest existing run tag automatically
458
+ cf harden user_proj_example --from OpenROAD.DetailedRouting
459
+
460
+ # Show exact valid --from step names for a macro
461
+ cf harden user_proj_example --list-from-steps
462
+
463
+ # Open GUI for a specific run tag
464
+ cf harden user_proj_example --tag test2 --open-in-openroad
465
+ cf harden user_proj_example --tag test2 --open-in-klayout
466
+
467
+ # Open GUI for latest existing run tag automatically
468
+ cf harden user_proj_example --open-in-openroad
469
+
451
470
  ```
452
471
 
453
472
  **Workflow:**
@@ -598,7 +617,7 @@ cf push [OPTIONS]
598
617
  **Options:**
599
618
  - `--project-root`: Specify project directory
600
619
  - `--force-overwrite`: Overwrite existing files on SFTP (SFTP mode only)
601
- - `--submit`: Submit the project for review after upload
620
+ - `--submit`: Submit the project for review after upload (shortcut for `cf push` + `cf submit`)
602
621
  - `--dry-run`: Preview what would be uploaded
603
622
  - `--sftp-username`: Override configured username (SFTP mode only)
604
623
  - `--sftp-key`: Override configured key path (SFTP mode only)
@@ -659,6 +678,15 @@ What happens:
659
678
  4. Staged S3 objects are deleted on success; any leftovers are expired by the bucket lifecycle after 7 days.
660
679
  5. `--submit` submits for review on success.
661
680
 
681
+ ### Submit a Project for Review (No Re-upload)
682
+
683
+ ```bash
684
+ cf submit [--project-root PATH]
685
+ ```
686
+
687
+ Use this when your latest `cf push` already uploaded the files you want reviewed.
688
+ `cf submit` moves the platform project into review without requiring another upload.
689
+
662
690
  > [!TIP]
663
691
  > Try modes in this order: `cf push` → `cf push --remote` → `cf push --https`.
664
692
  > The SFTP mode is fastest when unrestricted, `--remote` is the best HTTPS
@@ -401,13 +401,18 @@ cf harden [MACRO] [OPTIONS]
401
401
 
402
402
  **Key Options:**
403
403
  - `--list` or no argument: List all available macros
404
+ - `--list-from-steps`: List valid LibreLane step names for `--from` for a specific macro
404
405
  - `MACRO`: Name of macro to harden (e.g., `user_proj_example`, `user_project_wrapper`)
405
406
  - `--project-root`: Specify project directory
406
- - `--tag`: Custom run tag (defaults to timestamp)
407
+ - `--tag`: Run tag. Without `--from`, existing tag is overwritten; with `--from`, resumes that tag
408
+ - `--from`: Start hardening from a specific LibreLane step (e.g., `OpenROAD.DetailedRouting`)
409
+ - `--tag` + `--from`: Resume from that step using the existing run state under the specified tag
410
+ - `--from` without `--tag`: Resume from that step using the latest existing run tag for the macro
411
+ - `--open-in-openroad`: Open an existing run in OpenROAD GUI (uses latest run if `--tag` omitted)
412
+ - `--open-in-klayout`: Open an existing run in KLayout GUI (uses latest run if `--tag` omitted)
407
413
  - `--pdk`: PDK to use (default: sky130A)
408
414
  - `--use-nix`: Force use of Nix (fails if Nix not available)
409
415
  - `--use-docker`: Force use of Docker (fails if Docker not available)
410
- - `--dry-run`: Show configuration without running
411
416
 
412
417
  **Examples:**
413
418
  ```bash
@@ -420,8 +425,22 @@ cf harden user_proj_example
420
425
  # Harden with custom tag and PDK
421
426
  cf harden user_proj_example --tag my_run --pdk sky130B
422
427
 
423
- # Preview hardening configuration
424
- cf harden user_proj_example --dry-run
428
+ # Resume flow from a specific LibreLane step
429
+ cf harden user_proj_example --tag test2 --from OpenROAD.DetailedRouting
430
+
431
+ # Resume from latest existing run tag automatically
432
+ cf harden user_proj_example --from OpenROAD.DetailedRouting
433
+
434
+ # Show exact valid --from step names for a macro
435
+ cf harden user_proj_example --list-from-steps
436
+
437
+ # Open GUI for a specific run tag
438
+ cf harden user_proj_example --tag test2 --open-in-openroad
439
+ cf harden user_proj_example --tag test2 --open-in-klayout
440
+
441
+ # Open GUI for latest existing run tag automatically
442
+ cf harden user_proj_example --open-in-openroad
443
+
425
444
  ```
426
445
 
427
446
  **Workflow:**
@@ -572,7 +591,7 @@ cf push [OPTIONS]
572
591
  **Options:**
573
592
  - `--project-root`: Specify project directory
574
593
  - `--force-overwrite`: Overwrite existing files on SFTP (SFTP mode only)
575
- - `--submit`: Submit the project for review after upload
594
+ - `--submit`: Submit the project for review after upload (shortcut for `cf push` + `cf submit`)
576
595
  - `--dry-run`: Preview what would be uploaded
577
596
  - `--sftp-username`: Override configured username (SFTP mode only)
578
597
  - `--sftp-key`: Override configured key path (SFTP mode only)
@@ -633,6 +652,15 @@ What happens:
633
652
  4. Staged S3 objects are deleted on success; any leftovers are expired by the bucket lifecycle after 7 days.
634
653
  5. `--submit` submits for review on success.
635
654
 
655
+ ### Submit a Project for Review (No Re-upload)
656
+
657
+ ```bash
658
+ cf submit [--project-root PATH]
659
+ ```
660
+
661
+ Use this when your latest `cf push` already uploaded the files you want reviewed.
662
+ `cf submit` moves the platform project into review without requiring another upload.
663
+
636
664
  > [!TIP]
637
665
  > Try modes in this order: `cf push` → `cf push --remote` → `cf push --https`.
638
666
  > The SFTP mode is fastest when unrestricted, `--remote` is the best HTTPS
@@ -28,6 +28,7 @@ import subprocess
28
28
  import sys
29
29
  import shutil
30
30
  import signal
31
+ import difflib
31
32
 
32
33
  # Textual imports for GPIO grid UI
33
34
  from textual.app import App, ComposeResult
@@ -41,6 +42,42 @@ DEFAULT_SFTP_HOST = 'sftp.chipfoundry.io'
41
42
 
42
43
  console = Console()
43
44
 
45
+ class CategorizedCommand(click.Command):
46
+ """Click command with categorized help sections for options."""
47
+
48
+ def __init__(self, *args, option_categories=None, **kwargs):
49
+ self.option_categories = option_categories or []
50
+ super().__init__(*args, **kwargs)
51
+
52
+ def format_options(self, ctx, formatter):
53
+ options = {}
54
+ for param in self.get_params(ctx):
55
+ if not isinstance(param, click.Option):
56
+ continue
57
+ # Keep built-in --help available but omit it from custom sections.
58
+ if param.name == "help":
59
+ continue
60
+ record = param.get_help_record(ctx)
61
+ if record:
62
+ # Make option names easier to scan in help output.
63
+ options[param.name] = (click.style(record[0], fg="cyan"), record[1])
64
+
65
+ rendered = set()
66
+ for title, option_names in self.option_categories:
67
+ rows = []
68
+ for opt_name in option_names:
69
+ if opt_name in options:
70
+ rows.append(options[opt_name])
71
+ rendered.add(opt_name)
72
+ if rows:
73
+ with formatter.section(click.style(title, fg="green", bold=True)):
74
+ formatter.write_dl(rows)
75
+
76
+ remaining_rows = [row for name, row in options.items() if name not in rendered]
77
+ if remaining_rows:
78
+ with formatter.section(click.style("Other Options", fg="green", bold=True)):
79
+ formatter.write_dl(remaining_rows)
80
+
44
81
  def get_git_tag(repo_path):
45
82
  """Get the current git tag/branch of a repository."""
46
83
  try:
@@ -2860,6 +2897,45 @@ def view_tapeout_report(project_name, report_path):
2860
2897
  console.print(f"[red]Failed to open tapeout report in browser: {e}[/red]")
2861
2898
  raise click.Abort()
2862
2899
 
2900
+ @main.command("submit")
2901
+ @click.option(
2902
+ "--project-root",
2903
+ required=False,
2904
+ type=click.Path(exists=True, file_okay=False),
2905
+ help="Path to the local ChipFoundry project directory (defaults to current directory if .cf/project.json exists).",
2906
+ )
2907
+ def submit(project_root):
2908
+ """Submit a project for admin review without uploading files again."""
2909
+ cwd_root, _ = get_project_json_from_cwd()
2910
+ if not project_root and cwd_root:
2911
+ project_root = cwd_root
2912
+ if not project_root:
2913
+ console.print(
2914
+ "[red]No project root specified and no .cf/project.json found in current directory.[/red]"
2915
+ )
2916
+ console.print("Provide --project-root or run from a linked project.")
2917
+ raise click.Abort()
2918
+
2919
+ project_root = str(Path(project_root).resolve())
2920
+ platform_id = _load_project_platform_id(project_root)
2921
+ if not platform_id:
2922
+ console.print("[red]Project is not linked to the platform.[/red]")
2923
+ console.print(
2924
+ "Run [bold]cf link[/bold] to connect this project, or [bold]cf init[/bold] to create a new one."
2925
+ )
2926
+ raise click.Abort()
2927
+
2928
+ config = load_user_config()
2929
+ if not config.get("api_key"):
2930
+ console.print("[red]Not logged in.[/red] Run [bold]cf login[/bold] before submitting.")
2931
+ raise click.Abort()
2932
+
2933
+ try:
2934
+ _api_post(f"/projects/{platform_id}/submit", {})
2935
+ console.print("[green]✓ Project submitted for review[/green]")
2936
+ except SystemExit:
2937
+ raise click.Abort()
2938
+
2863
2939
  @main.command('confirm')
2864
2940
  @click.option('--project-root', required=False, type=click.Path(exists=True, file_okay=False), help='Path to the local ChipFoundry project directory (defaults to current directory if .cf/project.json exists).')
2865
2941
  @click.option('--sftp-host', default=DEFAULT_SFTP_HOST, show_default=True, help='SFTP server hostname.')
@@ -3620,23 +3696,29 @@ def setup(project_root, repo_owner, repo_name, branch, pdk, caravel_lite,
3620
3696
  else:
3621
3697
  console.print("[bold green]Setup complete![/bold green]")
3622
3698
 
3623
- @main.command('harden')
3699
+ @main.command(
3700
+ 'harden',
3701
+ cls=CategorizedCommand,
3702
+ option_categories=[
3703
+ ("Design Selection", ["project_root", "list_designs", "list_from_steps"]),
3704
+ ("Run Controls", ["tag", "from_step"]),
3705
+ ("GUI Modes", ["open_in_openroad", "open_in_klayout"]),
3706
+ ("Execution Backend", ["pdk", "use_nix", "use_docker"]),
3707
+ ],
3708
+ )
3624
3709
  @click.argument('macro', required=False)
3625
3710
  @click.option('--project-root', type=click.Path(exists=True, file_okay=False), help='Path to the project directory (defaults to current directory)')
3626
3711
  @click.option('--list', 'list_designs', is_flag=True, help='List all available macros')
3627
- @click.option('--tag', help='Custom run tag (defaults to timestamp)')
3712
+ @click.option('--list-from-steps', is_flag=True, help='List valid LibreLane step names for --from (requires MACRO)')
3713
+ @click.option('--tag', help='Run tag. Without --from, existing tag is overwritten; with --from, resumes that tag')
3714
+ @click.option('--from', 'from_step', help='Start hardening from a specific LibreLane step (uses latest run tag if --tag is omitted)')
3715
+ @click.option('--open-in-openroad', is_flag=True, help='Open an existing run in the OpenROAD GUI')
3716
+ @click.option('--open-in-klayout', is_flag=True, help='Open an existing run in the KLayout GUI')
3628
3717
  @click.option('--pdk', help='PDK to use (defaults to sky130A)')
3629
3718
  @click.option('--use-nix', is_flag=True, help='Force use of Nix (fails if Nix not available)')
3630
3719
  @click.option('--use-docker', is_flag=True, help='Force use of Docker (fails if Docker not available)')
3631
- @click.option('--dry-run', is_flag=True, help='Show the configuration without running')
3632
- def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry_run):
3633
- """Harden a macro using LibreLane (OpenLane 2).
3634
-
3635
- Examples:
3636
- cf harden user_proj_example # Harden a specific macro
3637
- cf harden user_project_wrapper
3638
- cf harden --list # List available macros
3639
- """
3720
+ def harden(macro, project_root, list_designs, list_from_steps, tag, from_step, open_in_openroad, open_in_klayout, pdk, use_nix, use_docker):
3721
+ """Harden a macro using LibreLane (OpenLane 2)."""
3640
3722
  from datetime import datetime
3641
3723
 
3642
3724
  # If .cf/project.json exists in cwd, use it as default project_root
@@ -3650,7 +3732,7 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3650
3732
 
3651
3733
  # Check if project is initialized (skip check for --list or when no macro specified, allow graceful return)
3652
3734
  if not list_designs and macro:
3653
- if not check_project_initialized(project_root_path, 'harden', dry_run=dry_run, allow_graceful=True):
3735
+ if not check_project_initialized(project_root_path, 'harden', allow_graceful=True):
3654
3736
  console.print(f"[red]✗[/red] Project not initialized. Please run 'cf init' first.")
3655
3737
  console.print("[yellow]Run 'cf setup' first to install OpenLane[/yellow]")
3656
3738
  return
@@ -3663,9 +3745,26 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3663
3745
  console.print("[yellow]Run 'cf setup' first to install OpenLane[/yellow]")
3664
3746
  return
3665
3747
 
3748
+ if list_from_steps and not macro:
3749
+ console.print("[red]✗[/red] --list-from-steps requires a macro name")
3750
+ console.print("[yellow]Example:[/yellow] cf harden user_proj_example --list-from-steps")
3751
+ return
3752
+
3753
+ gui_mode_count = int(open_in_openroad) + int(open_in_klayout)
3754
+ if gui_mode_count > 1:
3755
+ console.print("[red]✗[/red] Use only one GUI flag: --open-in-openroad or --open-in-klayout")
3756
+ return
3757
+ if gui_mode_count and from_step:
3758
+ console.print("[red]✗[/red] --from cannot be combined with GUI modes")
3759
+ console.print("[yellow]Use --tag to select which run to open, or omit --tag to use latest run[/yellow]")
3760
+ return
3761
+ if gui_mode_count and list_from_steps:
3762
+ console.print("[red]✗[/red] --list-from-steps cannot be combined with GUI modes")
3763
+ return
3764
+
3666
3765
  # If no macro specified, show prompt with available macros
3667
- no_macro_specified = not macro and not list_designs
3668
- if not macro:
3766
+ no_macro_specified = not macro and not list_designs and not list_from_steps
3767
+ if not macro and not list_from_steps:
3669
3768
  list_designs = True
3670
3769
 
3671
3770
  # List designs if requested (or if no macro specified)
@@ -3714,6 +3813,89 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3714
3813
  console.print("[red]✗[/red] LibreLane not installed")
3715
3814
  console.print("[yellow]Run 'cf setup --only-openlane' to install LibreLane[/yellow]")
3716
3815
  raise click.Abort()
3816
+
3817
+ # Resolve valid step names for this macro's selected flow using LibreLane itself.
3818
+ def get_valid_from_steps(librelane_python, macro_config, working_dir):
3819
+ script = (
3820
+ "import json, sys\n"
3821
+ "from librelane.config import Config\n"
3822
+ "from librelane.flows import Flow, SequentialFlow\n"
3823
+ "cfg = sys.argv[1]\n"
3824
+ "target = Flow.factory.get('Classic')\n"
3825
+ "meta = Config.get_meta(cfg)\n"
3826
+ "if meta:\n"
3827
+ " if isinstance(meta.flow, str):\n"
3828
+ " found = Flow.factory.get(meta.flow)\n"
3829
+ " if found is None:\n"
3830
+ " raise RuntimeError(f\"Unknown flow '{meta.flow}' in config metadata\")\n"
3831
+ " target = found\n"
3832
+ " elif isinstance(meta.flow, list):\n"
3833
+ " target = SequentialFlow.make(meta.flow)\n"
3834
+ " if meta.substituting_steps is not None:\n"
3835
+ " if meta.flow is None:\n"
3836
+ " raise RuntimeError('substituting_steps is set but flow is not defined')\n"
3837
+ " if not issubclass(target, SequentialFlow):\n"
3838
+ " raise RuntimeError('substituting_steps requires a sequential flow')\n"
3839
+ " target = target.Substitute(meta.substituting_steps)\n"
3840
+ "steps = []\n"
3841
+ "seen = set()\n"
3842
+ "for step in getattr(target, 'Steps', []) or []:\n"
3843
+ " step_id = getattr(step, 'id', None)\n"
3844
+ " if not step_id:\n"
3845
+ " continue\n"
3846
+ " if step_id in seen:\n"
3847
+ " continue\n"
3848
+ " seen.add(step_id)\n"
3849
+ " steps.append(step_id)\n"
3850
+ "print(json.dumps({'steps': steps}))\n"
3851
+ )
3852
+ result = subprocess.run(
3853
+ [str(librelane_python), '-c', script, macro_config],
3854
+ cwd=str(working_dir),
3855
+ capture_output=True,
3856
+ text=True,
3857
+ )
3858
+ if result.returncode != 0:
3859
+ err = (result.stderr or result.stdout or 'unknown error').strip()
3860
+ return None, err
3861
+ try:
3862
+ payload = json.loads(result.stdout.strip())
3863
+ return payload.get('steps', []), None
3864
+ except Exception as exc:
3865
+ return None, str(exc)
3866
+
3867
+ venv_bin = librelane_venv / 'bin'
3868
+ librelane_python = venv_bin / 'python3'
3869
+ valid_from_steps, valid_from_steps_error = get_valid_from_steps(
3870
+ librelane_python=librelane_python,
3871
+ macro_config=config_file,
3872
+ working_dir=openlane_dir,
3873
+ )
3874
+ if valid_from_steps_error:
3875
+ console.print("[red]✗[/red] Failed to load LibreLane step list for this macro")
3876
+ console.print(f"[yellow]Error:[/yellow] {valid_from_steps_error}")
3877
+ console.print("[yellow]Run 'cf setup --only-openlane' to ensure LibreLane is installed correctly[/yellow]")
3878
+ return
3879
+
3880
+ if list_from_steps:
3881
+ console.print(f"[bold cyan]Valid --from steps for {macro}:[/bold cyan]")
3882
+ if valid_from_steps:
3883
+ for step_name in valid_from_steps:
3884
+ console.print(f" • {step_name}")
3885
+ else:
3886
+ console.print("[yellow]No steps found for this flow[/yellow]")
3887
+ return
3888
+
3889
+ if from_step and from_step not in valid_from_steps:
3890
+ console.print(f"[red]✗[/red] Invalid --from step: {from_step}")
3891
+ matches = difflib.get_close_matches(from_step, valid_from_steps, n=5, cutoff=0.4)
3892
+ if matches:
3893
+ console.print("[yellow]Did you mean:[/yellow]")
3894
+ for m in matches:
3895
+ console.print(f" • {m}")
3896
+ else:
3897
+ console.print(f"[yellow]Use 'cf harden {macro} --list-from-steps' to see valid step names[/yellow]")
3898
+ return
3717
3899
 
3718
3900
  # Fetch versions from upstream
3719
3901
  console.print("[dim]Fetching version information from cf-cli repository...[/dim]")
@@ -3810,7 +3992,28 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3810
3992
  console.print("[yellow]Run 'cf setup --only-pdk' to install the PDK[/yellow]")
3811
3993
  return
3812
3994
 
3813
- if not tag:
3995
+ auto_selected_latest_tag = False
3996
+ if (from_step or gui_mode_count) and not tag:
3997
+ runs_dir = macro_dir / 'runs'
3998
+ if not runs_dir.exists():
3999
+ console.print("[red]✗[/red] No existing runs found for this macro")
4000
+ if gui_mode_count:
4001
+ console.print("[yellow]Create a hardening run first, or specify --tag <existing_tag>[/yellow]")
4002
+ else:
4003
+ console.print("[yellow]Run without --from first, or specify --tag <existing_tag>[/yellow]")
4004
+ return
4005
+ candidate_runs = [p for p in runs_dir.iterdir() if p.is_dir()]
4006
+ if not candidate_runs:
4007
+ console.print("[red]✗[/red] No existing runs found for this macro")
4008
+ if gui_mode_count:
4009
+ console.print("[yellow]Create a hardening run first, or specify --tag <existing_tag>[/yellow]")
4010
+ else:
4011
+ console.print("[yellow]Run without --from first, or specify --tag <existing_tag>[/yellow]")
4012
+ return
4013
+ latest_run = max(candidate_runs, key=lambda p: p.stat().st_mtime)
4014
+ tag = latest_run.name
4015
+ auto_selected_latest_tag = True
4016
+ elif not tag:
3814
4017
  tag = datetime.now().strftime('%y_%m_%d_%H_%M')
3815
4018
 
3816
4019
  # Display configuration
@@ -3818,16 +4021,23 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3818
4021
  console.print(f"[bold cyan]Hardening: {macro}[/bold cyan]")
3819
4022
  console.print(f"Config: [yellow]{Path(config_file).name}[/yellow]")
3820
4023
  console.print(f"Run tag: [yellow]{tag}[/yellow]")
4024
+ if auto_selected_latest_tag:
4025
+ if gui_mode_count:
4026
+ console.print("[yellow]Using latest existing run tag (auto-selected because GUI mode was requested without --tag)[/yellow]")
4027
+ else:
4028
+ console.print("[yellow]Using latest existing run tag (auto-selected because --from was provided without --tag)[/yellow]")
4029
+ if from_step:
4030
+ console.print(f"Start from: [yellow]{from_step}[/yellow]")
4031
+ console.print("[yellow]Mode:[/yellow] resume from existing state under this tag (no overwrite)")
4032
+ if open_in_openroad:
4033
+ console.print("[yellow]Mode:[/yellow] open existing run in OpenROAD GUI")
4034
+ elif open_in_klayout:
4035
+ console.print("[yellow]Mode:[/yellow] open existing run in KLayout GUI")
3821
4036
  console.print(f"PDK: [yellow]{pdk}[/yellow]")
3822
4037
  console.print(f"PDK Root: [yellow]{pdk_root}[/yellow]")
3823
4038
  console.print(f"Execution: [yellow]{execution_method}[/yellow]")
3824
4039
  console.print("="*60 + "\n")
3825
-
3826
- if dry_run:
3827
- console.print("[bold yellow]Dry run - configuration ready[/bold yellow]")
3828
- console.print(f"Would use: {execution_method}")
3829
- return
3830
-
4040
+
3831
4041
  # Build command based on execution method
3832
4042
  if use_nix:
3833
4043
  # Use Nix to run LibreLane
@@ -3835,22 +4045,30 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3835
4045
 
3836
4046
  cmd = [
3837
4047
  'nix', 'run', f'github:chipfoundry/openlane-2/{openlane_version}', '--',
3838
- '--run-tag', tag,
3839
4048
  '--manual-pdk',
3840
4049
  '--pdk-root', str(pdk_root),
3841
4050
  '--pdk', pdk,
3842
4051
  '--ef-save-views-to', str(project_root_path),
3843
- '--overwrite',
3844
- config_file
4052
+ '--run-tag', tag,
3845
4053
  ]
4054
+ if open_in_openroad:
4055
+ cmd.extend(['--flow', 'OpenInOpenROAD'])
4056
+ elif open_in_klayout:
4057
+ cmd.extend(['--flow', 'OpenInKLayout'])
4058
+ elif not from_step:
4059
+ cmd.append('--overwrite')
4060
+ if from_step:
4061
+ cmd.extend(['--from', from_step])
4062
+ cmd.append(config_file)
3846
4063
 
3847
4064
  env = os.environ.copy()
3848
4065
  env.update({
3849
4066
  'PROJECT_ROOT': str(project_root_path),
3850
4067
  'PDK_ROOT': str(pdk_root),
3851
4068
  'PDK': pdk,
3852
- 'LIBRELANE_RUN_TAG': tag,
3853
4069
  })
4070
+ if tag:
4071
+ env['LIBRELANE_RUN_TAG'] = tag
3854
4072
 
3855
4073
  else:
3856
4074
  # Use Docker via venv
@@ -3862,12 +4080,12 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3862
4080
  'PROJECT_ROOT': str(project_root_path),
3863
4081
  'PDK_ROOT': str(pdk_root),
3864
4082
  'PDK': pdk,
3865
- 'LIBRELANE_RUN_TAG': tag,
3866
4083
  'PYTHONPATH': str(librelane_venv / 'lib' / f'python{sys.version_info.major}.{sys.version_info.minor}' / 'site-packages')
3867
4084
  })
4085
+ if tag:
4086
+ env['LIBRELANE_RUN_TAG'] = tag
3868
4087
 
3869
4088
  # Add venv to PATH so librelane can find its dependencies
3870
- venv_bin = librelane_venv / 'bin'
3871
4089
  env['PATH'] = f"{venv_bin}:{env.get('PATH', '')}"
3872
4090
 
3873
4091
  # Build LibreLane command
@@ -3888,14 +4106,21 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3888
4106
  cmd.append('--docker-no-tty')
3889
4107
 
3890
4108
  cmd.extend([
3891
- '--run-tag', tag,
3892
4109
  '--manual-pdk',
3893
4110
  '--pdk-root', str(pdk_root),
3894
4111
  '--pdk', pdk,
3895
4112
  '--ef-save-views-to', str(project_root_path),
3896
- '--overwrite',
3897
- config_file
4113
+ '--run-tag', tag,
3898
4114
  ])
4115
+ if open_in_openroad:
4116
+ cmd.extend(['--flow', 'OpenInOpenROAD'])
4117
+ elif open_in_klayout:
4118
+ cmd.extend(['--flow', 'OpenInKLayout'])
4119
+ elif not from_step:
4120
+ cmd.append('--overwrite')
4121
+ if from_step:
4122
+ cmd.extend(['--from', from_step])
4123
+ cmd.append(config_file)
3899
4124
 
3900
4125
  # Run LibreLane
3901
4126
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "chipfoundry-cli"
3
- version = "2.4.9"
3
+ version = "2.5.0"
4
4
  description = "CLI tool to automate ChipFoundry project submission to SFTP server"
5
5
  authors = ["ChipFoundry <marwan.abbas@chipfoundry.io>"]
6
6
  readme = "README.md"
File without changes