chipfoundry-cli 2.4.10__tar.gz → 2.5.1__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.10
3
+ Version: 2.5.1
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:**
@@ -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:**
@@ -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:
@@ -3659,23 +3696,29 @@ def setup(project_root, repo_owner, repo_name, branch, pdk, caravel_lite,
3659
3696
  else:
3660
3697
  console.print("[bold green]Setup complete![/bold green]")
3661
3698
 
3662
- @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
+ )
3663
3709
  @click.argument('macro', required=False)
3664
3710
  @click.option('--project-root', type=click.Path(exists=True, file_okay=False), help='Path to the project directory (defaults to current directory)')
3665
3711
  @click.option('--list', 'list_designs', is_flag=True, help='List all available macros')
3666
- @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')
3667
3717
  @click.option('--pdk', help='PDK to use (defaults to sky130A)')
3668
3718
  @click.option('--use-nix', is_flag=True, help='Force use of Nix (fails if Nix not available)')
3669
3719
  @click.option('--use-docker', is_flag=True, help='Force use of Docker (fails if Docker not available)')
3670
- @click.option('--dry-run', is_flag=True, help='Show the configuration without running')
3671
- def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry_run):
3672
- """Harden a macro using LibreLane (OpenLane 2).
3673
-
3674
- Examples:
3675
- cf harden user_proj_example # Harden a specific macro
3676
- cf harden user_project_wrapper
3677
- cf harden --list # List available macros
3678
- """
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)."""
3679
3722
  from datetime import datetime
3680
3723
 
3681
3724
  # If .cf/project.json exists in cwd, use it as default project_root
@@ -3689,7 +3732,7 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3689
3732
 
3690
3733
  # Check if project is initialized (skip check for --list or when no macro specified, allow graceful return)
3691
3734
  if not list_designs and macro:
3692
- 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):
3693
3736
  console.print(f"[red]✗[/red] Project not initialized. Please run 'cf init' first.")
3694
3737
  console.print("[yellow]Run 'cf setup' first to install OpenLane[/yellow]")
3695
3738
  return
@@ -3702,9 +3745,26 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3702
3745
  console.print("[yellow]Run 'cf setup' first to install OpenLane[/yellow]")
3703
3746
  return
3704
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
+
3705
3765
  # If no macro specified, show prompt with available macros
3706
- no_macro_specified = not macro and not list_designs
3707
- 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:
3708
3768
  list_designs = True
3709
3769
 
3710
3770
  # List designs if requested (or if no macro specified)
@@ -3753,6 +3813,89 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3753
3813
  console.print("[red]✗[/red] LibreLane not installed")
3754
3814
  console.print("[yellow]Run 'cf setup --only-openlane' to install LibreLane[/yellow]")
3755
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
3756
3899
 
3757
3900
  # Fetch versions from upstream
3758
3901
  console.print("[dim]Fetching version information from cf-cli repository...[/dim]")
@@ -3849,7 +3992,28 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3849
3992
  console.print("[yellow]Run 'cf setup --only-pdk' to install the PDK[/yellow]")
3850
3993
  return
3851
3994
 
3852
- 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:
3853
4017
  tag = datetime.now().strftime('%y_%m_%d_%H_%M')
3854
4018
 
3855
4019
  # Display configuration
@@ -3857,16 +4021,23 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3857
4021
  console.print(f"[bold cyan]Hardening: {macro}[/bold cyan]")
3858
4022
  console.print(f"Config: [yellow]{Path(config_file).name}[/yellow]")
3859
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")
3860
4036
  console.print(f"PDK: [yellow]{pdk}[/yellow]")
3861
4037
  console.print(f"PDK Root: [yellow]{pdk_root}[/yellow]")
3862
4038
  console.print(f"Execution: [yellow]{execution_method}[/yellow]")
3863
4039
  console.print("="*60 + "\n")
3864
-
3865
- if dry_run:
3866
- console.print("[bold yellow]Dry run - configuration ready[/bold yellow]")
3867
- console.print(f"Would use: {execution_method}")
3868
- return
3869
-
4040
+
3870
4041
  # Build command based on execution method
3871
4042
  if use_nix:
3872
4043
  # Use Nix to run LibreLane
@@ -3874,22 +4045,30 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3874
4045
 
3875
4046
  cmd = [
3876
4047
  'nix', 'run', f'github:chipfoundry/openlane-2/{openlane_version}', '--',
3877
- '--run-tag', tag,
3878
4048
  '--manual-pdk',
3879
4049
  '--pdk-root', str(pdk_root),
3880
4050
  '--pdk', pdk,
3881
4051
  '--ef-save-views-to', str(project_root_path),
3882
- '--overwrite',
3883
- config_file
4052
+ '--run-tag', tag,
3884
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)
3885
4063
 
3886
4064
  env = os.environ.copy()
3887
4065
  env.update({
3888
4066
  'PROJECT_ROOT': str(project_root_path),
3889
4067
  'PDK_ROOT': str(pdk_root),
3890
4068
  'PDK': pdk,
3891
- 'LIBRELANE_RUN_TAG': tag,
3892
4069
  })
4070
+ if tag:
4071
+ env['LIBRELANE_RUN_TAG'] = tag
3893
4072
 
3894
4073
  else:
3895
4074
  # Use Docker via venv
@@ -3901,12 +4080,12 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3901
4080
  'PROJECT_ROOT': str(project_root_path),
3902
4081
  'PDK_ROOT': str(pdk_root),
3903
4082
  'PDK': pdk,
3904
- 'LIBRELANE_RUN_TAG': tag,
3905
4083
  'PYTHONPATH': str(librelane_venv / 'lib' / f'python{sys.version_info.major}.{sys.version_info.minor}' / 'site-packages')
3906
4084
  })
4085
+ if tag:
4086
+ env['LIBRELANE_RUN_TAG'] = tag
3907
4087
 
3908
4088
  # Add venv to PATH so librelane can find its dependencies
3909
- venv_bin = librelane_venv / 'bin'
3910
4089
  env['PATH'] = f"{venv_bin}:{env.get('PATH', '')}"
3911
4090
 
3912
4091
  # Build LibreLane command
@@ -3915,7 +4094,6 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3915
4094
  str(venv_bin / 'python3'), '-m', 'librelane',
3916
4095
  '-m', str(project_root_path),
3917
4096
  '-m', str(pdk_root),
3918
- '--dockerized',
3919
4097
  ]
3920
4098
 
3921
4099
  # Add --docker-no-tty if not running in a TTY (e.g., CI environments)
@@ -3925,16 +4103,26 @@ def harden(macro, project_root, list_designs, tag, pdk, use_nix, use_docker, dry
3925
4103
  except:
3926
4104
  # If we can't detect TTY, assume non-TTY (safer for CI)
3927
4105
  cmd.append('--docker-no-tty')
3928
-
4106
+
4107
+ # --docker-no-tty must come before --dockerized
4108
+ cmd.append('--dockerized')
4109
+
3929
4110
  cmd.extend([
3930
- '--run-tag', tag,
3931
4111
  '--manual-pdk',
3932
4112
  '--pdk-root', str(pdk_root),
3933
4113
  '--pdk', pdk,
3934
4114
  '--ef-save-views-to', str(project_root_path),
3935
- '--overwrite',
3936
- config_file
4115
+ '--run-tag', tag,
3937
4116
  ])
4117
+ if open_in_openroad:
4118
+ cmd.extend(['--flow', 'OpenInOpenROAD'])
4119
+ elif open_in_klayout:
4120
+ cmd.extend(['--flow', 'OpenInKLayout'])
4121
+ elif not from_step:
4122
+ cmd.append('--overwrite')
4123
+ if from_step:
4124
+ cmd.extend(['--from', from_step])
4125
+ cmd.append(config_file)
3938
4126
 
3939
4127
  # Run LibreLane
3940
4128
 
@@ -5206,4 +5394,4 @@ def whoami_cmd():
5206
5394
 
5207
5395
 
5208
5396
  if __name__ == "__main__":
5209
- main()
5397
+ main()
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "chipfoundry-cli"
3
- version = "2.4.10"
3
+ version = "2.5.1"
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"