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.
- {chipfoundry_cli-2.4.9 → chipfoundry_cli-2.5.0}/PKG-INFO +34 -6
- {chipfoundry_cli-2.4.9 → chipfoundry_cli-2.5.0}/README.md +33 -5
- {chipfoundry_cli-2.4.9 → chipfoundry_cli-2.5.0}/chipfoundry_cli/main.py +255 -30
- {chipfoundry_cli-2.4.9 → chipfoundry_cli-2.5.0}/pyproject.toml +1 -1
- {chipfoundry_cli-2.4.9 → chipfoundry_cli-2.5.0}/LICENSE +0 -0
- {chipfoundry_cli-2.4.9 → chipfoundry_cli-2.5.0}/chipfoundry_cli/__init__.py +0 -0
- {chipfoundry_cli-2.4.9 → chipfoundry_cli-2.5.0}/chipfoundry_cli/check_refs.py +0 -0
- {chipfoundry_cli-2.4.9 → chipfoundry_cli-2.5.0}/chipfoundry_cli/remote_precheck_git.py +0 -0
- {chipfoundry_cli-2.4.9 → chipfoundry_cli-2.5.0}/chipfoundry_cli/utils.py +0 -0
- {chipfoundry_cli-2.4.9 → chipfoundry_cli-2.5.0}/chipfoundry_cli/version_check.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: chipfoundry-cli
|
|
3
|
-
Version: 2.
|
|
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`:
|
|
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
|
-
#
|
|
450
|
-
cf harden user_proj_example --
|
|
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`:
|
|
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
|
-
#
|
|
424
|
-
cf harden user_proj_example --
|
|
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(
|
|
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('--
|
|
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
|
-
|
|
3632
|
-
|
|
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',
|
|
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
|
-
|
|
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
|
-
'--
|
|
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
|
-
'--
|
|
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
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|