chipfoundry-cli 1.0.4__tar.gz → 1.1.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-1.0.4 → chipfoundry_cli-1.1.0}/PKG-INFO +137 -2
- {chipfoundry_cli-1.0.4 → chipfoundry_cli-1.1.0}/README.md +135 -1
- {chipfoundry_cli-1.0.4 → chipfoundry_cli-1.1.0}/chipfoundry_cli/main.py +183 -3
- {chipfoundry_cli-1.0.4 → chipfoundry_cli-1.1.0}/chipfoundry_cli/utils.py +122 -5
- {chipfoundry_cli-1.0.4 → chipfoundry_cli-1.1.0}/pyproject.toml +3 -2
- {chipfoundry_cli-1.0.4 → chipfoundry_cli-1.1.0}/LICENSE +0 -0
- {chipfoundry_cli-1.0.4 → chipfoundry_cli-1.1.0}/chipfoundry_cli/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: chipfoundry-cli
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.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
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
18
|
Requires-Dist: click (>=8.0.0,<9)
|
|
19
|
+
Requires-Dist: httpx (>=0.24.0,<1.0)
|
|
19
20
|
Requires-Dist: paramiko (>=3.0.0,<4)
|
|
20
21
|
Requires-Dist: rich (>=13,<14)
|
|
21
22
|
Requires-Dist: toml (>=0.10,<1.0)
|
|
@@ -82,6 +83,11 @@ cf --help
|
|
|
82
83
|
cf view-tapeout-report
|
|
83
84
|
```
|
|
84
85
|
|
|
86
|
+
8. **Confirm final tapeout** (when ready to send GDS to foundry):
|
|
87
|
+
```bash
|
|
88
|
+
cf confirm
|
|
89
|
+
```
|
|
90
|
+
|
|
85
91
|
---
|
|
86
92
|
|
|
87
93
|
## Project Structure Requirements
|
|
@@ -95,6 +101,7 @@ Your project directory **must** contain:
|
|
|
95
101
|
- **Note**: Both compressed (`.gz`) and uncompressed (`.gds`) files are supported
|
|
96
102
|
- `verilog/rtl/user_defines.v` (required for digital/analog)
|
|
97
103
|
- `.cf/project.json` (optional; will be created/updated automatically)
|
|
104
|
+
- Contains project metadata including `submission_state` ("Draft" or "Final")
|
|
98
105
|
|
|
99
106
|
**Example:**
|
|
100
107
|
```
|
|
@@ -217,6 +224,23 @@ cf pull [--project-name NAME]
|
|
|
217
224
|
└── consolidated_report.html
|
|
218
225
|
```
|
|
219
226
|
|
|
227
|
+
### Confirm Final Tapeout
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
cf confirm [OPTIONS]
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
- **Confirms your final tapeout** by setting `submission_state` to "Final"
|
|
234
|
+
- **Uploads only the project.json** to the SFTP server (not the entire project)
|
|
235
|
+
- **Use this when you're ready to send your current GDS file to the foundry** for tapeout processing
|
|
236
|
+
- **Options:**
|
|
237
|
+
- `--project-root`: Specify project directory
|
|
238
|
+
- `--project-name`: Override project name
|
|
239
|
+
- `--sftp-username`: Override configured username
|
|
240
|
+
- `--sftp-key`: Override configured key path
|
|
241
|
+
|
|
242
|
+
**Important:** This command confirms that your current GDS file is ready to be sent to the foundry for tapeout. Only run this when you are completely satisfied with your design and ready for final tapeout processing. This action cannot be easily undone.
|
|
243
|
+
|
|
220
244
|
### View Tapeout Report
|
|
221
245
|
|
|
222
246
|
```bash
|
|
@@ -240,6 +264,102 @@ cf status
|
|
|
240
264
|
- Shows which projects have input files and/or results
|
|
241
265
|
- Displays project status in a clean table format
|
|
242
266
|
|
|
267
|
+
### Repository Management
|
|
268
|
+
|
|
269
|
+
#### Update Repository Files
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
cf repo update [OPTIONS]
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
- **Updates local project files** from the upstream GitHub repository
|
|
276
|
+
- **Fetches configuration** from `.cf/repo.json` in the specified repository and branch
|
|
277
|
+
- **Downloads and overwrites** local files based on the changes list in the configuration
|
|
278
|
+
- **Saves configuration** locally to `.cf/repo.json` for reference
|
|
279
|
+
|
|
280
|
+
**Options:**
|
|
281
|
+
- `--project-root`: Specify project directory (defaults to current directory)
|
|
282
|
+
- `--repo-owner`: GitHub repository owner (default: chipfoundry)
|
|
283
|
+
- `--repo-name`: GitHub repository name (default: caravel_user_project)
|
|
284
|
+
- `--branch`: Branch name containing the repo.json file (default: cli-update)
|
|
285
|
+
- `--dry-run`: Preview changes without updating files
|
|
286
|
+
|
|
287
|
+
**What happens:**
|
|
288
|
+
1. Fetches `.cf/repo.json` from the specified GitHub repository and branch
|
|
289
|
+
2. Downloads and saves the configuration file locally to `.cf/repo.json`
|
|
290
|
+
3. Downloads and overwrites each file listed in the `changes` array
|
|
291
|
+
4. Provides detailed feedback on success/failure of each file update
|
|
292
|
+
|
|
293
|
+
**Example:**
|
|
294
|
+
```bash
|
|
295
|
+
# Preview what would be updated
|
|
296
|
+
cf repo update --dry-run
|
|
297
|
+
# Output:
|
|
298
|
+
# Files that would be updated:
|
|
299
|
+
# • .cf/repo.json (configuration file)
|
|
300
|
+
# • README.md
|
|
301
|
+
# • Makefile
|
|
302
|
+
# • openlane/Makefile
|
|
303
|
+
|
|
304
|
+
# Update files from upstream
|
|
305
|
+
cf repo update
|
|
306
|
+
# Output:
|
|
307
|
+
# Update results:
|
|
308
|
+
# ✓ Updated: .cf/repo.json
|
|
309
|
+
# ✓ Updated: README.md
|
|
310
|
+
# ✓ Updated: Makefile
|
|
311
|
+
# ✓ Updated: openlane/Makefile
|
|
312
|
+
# Successfully updated 4 file(s)
|
|
313
|
+
# All files updated successfully!
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Use cases:**
|
|
317
|
+
- Keep your project up-to-date with upstream changes
|
|
318
|
+
- Apply bug fixes and improvements from the template repository
|
|
319
|
+
- Sync configuration changes from the upstream project
|
|
320
|
+
- Maintain consistency with the latest project structure
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Submission Workflow and States
|
|
325
|
+
|
|
326
|
+
The CLI tracks your project submission state through the `submission_state` field in `project.json`:
|
|
327
|
+
|
|
328
|
+
### Project States
|
|
329
|
+
|
|
330
|
+
- **"Draft"** - Initial state when you run `cf init`
|
|
331
|
+
- Project is ready for development and testing
|
|
332
|
+
- You can push updates multiple times
|
|
333
|
+
- Project is not yet ready for tapeout processing
|
|
334
|
+
|
|
335
|
+
- **"Final"** - Confirmed state when you run `cf confirm`
|
|
336
|
+
- GDS file is ready to be sent to the foundry for tapeout
|
|
337
|
+
- No further changes should be made to the design
|
|
338
|
+
- Only the project.json is uploaded (not the full project)
|
|
339
|
+
|
|
340
|
+
### Recommended Workflow
|
|
341
|
+
|
|
342
|
+
1. **Development Phase:**
|
|
343
|
+
```bash
|
|
344
|
+
cf init # Creates project with submission_state: "Draft"
|
|
345
|
+
cf push # Upload project files (state remains "Draft")
|
|
346
|
+
# ... make changes to your project ...
|
|
347
|
+
cf push # Upload updated files (state remains "Draft")
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
2. **Review Phase (Optional):**
|
|
351
|
+
```bash
|
|
352
|
+
cf pull # Download results for review (if available)
|
|
353
|
+
cf view-tapeout-report # Review the tapeout report (if available)
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
3. **Final Tapeout Confirmation:**
|
|
357
|
+
```bash
|
|
358
|
+
cf confirm # Confirm current GDS file is ready for foundry tapeout
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Important:** Only run `cf confirm` when you are completely satisfied with your GDS file and ready to send it to the foundry for tapeout processing. This action cannot be easily undone.
|
|
362
|
+
|
|
243
363
|
---
|
|
244
364
|
|
|
245
365
|
## How the GDS Hash Works
|
|
@@ -327,9 +447,15 @@ cf pull
|
|
|
327
447
|
# ✓ All files downloaded to sftp-output/my_awesome_project
|
|
328
448
|
# ✓ Project config automatically updated
|
|
329
449
|
|
|
330
|
-
#
|
|
450
|
+
# Review the tapeout report (if available)
|
|
331
451
|
cf view-tapeout-report
|
|
332
452
|
# ✓ Opened tapeout report in browser: sftp-output/my_awesome_project/consolidated_reports/consolidated_report.html
|
|
453
|
+
|
|
454
|
+
# When ready, confirm final tapeout (sends GDS to foundry)
|
|
455
|
+
cf confirm
|
|
456
|
+
# ✓ Updated project.json with submission_state = Final
|
|
457
|
+
# ✓ Confirmed project submission: my_awesome_project
|
|
458
|
+
# ✓ Uploaded project.json to incoming/projects/my_awesome_project/.cf/project.json
|
|
333
459
|
```
|
|
334
460
|
|
|
335
461
|
### Advanced Usage
|
|
@@ -350,6 +476,9 @@ cf pull --project-name other_project
|
|
|
350
476
|
# View report for specific project
|
|
351
477
|
cf view-tapeout-report --project-name other_project
|
|
352
478
|
|
|
479
|
+
# Confirm final tapeout for specific project
|
|
480
|
+
cf confirm --project-name other_project
|
|
481
|
+
|
|
353
482
|
# View custom report file
|
|
354
483
|
cf view-tapeout-report --report-path /path/to/custom_report.html
|
|
355
484
|
|
|
@@ -397,6 +526,12 @@ gds/user_project_wrapper.gds.gz # ← Remove this one
|
|
|
397
526
|
- Check that the report exists at the expected location
|
|
398
527
|
- Use `--report-path` to specify a custom report location
|
|
399
528
|
|
|
529
|
+
- **Repository update errors:**
|
|
530
|
+
- Check your internet connection
|
|
531
|
+
- Verify the repository and branch exist
|
|
532
|
+
- Use `--dry-run` to preview changes before applying them
|
|
533
|
+
- Check that the `.cf/repo.json` file exists in the upstream repository
|
|
534
|
+
|
|
400
535
|
- **ModuleNotFoundError:**
|
|
401
536
|
- Upgrade the CLI: `pip install --upgrade chipfoundry-cli`
|
|
402
537
|
|
|
@@ -58,6 +58,11 @@ cf --help
|
|
|
58
58
|
cf view-tapeout-report
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
8. **Confirm final tapeout** (when ready to send GDS to foundry):
|
|
62
|
+
```bash
|
|
63
|
+
cf confirm
|
|
64
|
+
```
|
|
65
|
+
|
|
61
66
|
---
|
|
62
67
|
|
|
63
68
|
## Project Structure Requirements
|
|
@@ -71,6 +76,7 @@ Your project directory **must** contain:
|
|
|
71
76
|
- **Note**: Both compressed (`.gz`) and uncompressed (`.gds`) files are supported
|
|
72
77
|
- `verilog/rtl/user_defines.v` (required for digital/analog)
|
|
73
78
|
- `.cf/project.json` (optional; will be created/updated automatically)
|
|
79
|
+
- Contains project metadata including `submission_state` ("Draft" or "Final")
|
|
74
80
|
|
|
75
81
|
**Example:**
|
|
76
82
|
```
|
|
@@ -193,6 +199,23 @@ cf pull [--project-name NAME]
|
|
|
193
199
|
└── consolidated_report.html
|
|
194
200
|
```
|
|
195
201
|
|
|
202
|
+
### Confirm Final Tapeout
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
cf confirm [OPTIONS]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- **Confirms your final tapeout** by setting `submission_state` to "Final"
|
|
209
|
+
- **Uploads only the project.json** to the SFTP server (not the entire project)
|
|
210
|
+
- **Use this when you're ready to send your current GDS file to the foundry** for tapeout processing
|
|
211
|
+
- **Options:**
|
|
212
|
+
- `--project-root`: Specify project directory
|
|
213
|
+
- `--project-name`: Override project name
|
|
214
|
+
- `--sftp-username`: Override configured username
|
|
215
|
+
- `--sftp-key`: Override configured key path
|
|
216
|
+
|
|
217
|
+
**Important:** This command confirms that your current GDS file is ready to be sent to the foundry for tapeout. Only run this when you are completely satisfied with your design and ready for final tapeout processing. This action cannot be easily undone.
|
|
218
|
+
|
|
196
219
|
### View Tapeout Report
|
|
197
220
|
|
|
198
221
|
```bash
|
|
@@ -216,6 +239,102 @@ cf status
|
|
|
216
239
|
- Shows which projects have input files and/or results
|
|
217
240
|
- Displays project status in a clean table format
|
|
218
241
|
|
|
242
|
+
### Repository Management
|
|
243
|
+
|
|
244
|
+
#### Update Repository Files
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
cf repo update [OPTIONS]
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
- **Updates local project files** from the upstream GitHub repository
|
|
251
|
+
- **Fetches configuration** from `.cf/repo.json` in the specified repository and branch
|
|
252
|
+
- **Downloads and overwrites** local files based on the changes list in the configuration
|
|
253
|
+
- **Saves configuration** locally to `.cf/repo.json` for reference
|
|
254
|
+
|
|
255
|
+
**Options:**
|
|
256
|
+
- `--project-root`: Specify project directory (defaults to current directory)
|
|
257
|
+
- `--repo-owner`: GitHub repository owner (default: chipfoundry)
|
|
258
|
+
- `--repo-name`: GitHub repository name (default: caravel_user_project)
|
|
259
|
+
- `--branch`: Branch name containing the repo.json file (default: cli-update)
|
|
260
|
+
- `--dry-run`: Preview changes without updating files
|
|
261
|
+
|
|
262
|
+
**What happens:**
|
|
263
|
+
1. Fetches `.cf/repo.json` from the specified GitHub repository and branch
|
|
264
|
+
2. Downloads and saves the configuration file locally to `.cf/repo.json`
|
|
265
|
+
3. Downloads and overwrites each file listed in the `changes` array
|
|
266
|
+
4. Provides detailed feedback on success/failure of each file update
|
|
267
|
+
|
|
268
|
+
**Example:**
|
|
269
|
+
```bash
|
|
270
|
+
# Preview what would be updated
|
|
271
|
+
cf repo update --dry-run
|
|
272
|
+
# Output:
|
|
273
|
+
# Files that would be updated:
|
|
274
|
+
# • .cf/repo.json (configuration file)
|
|
275
|
+
# • README.md
|
|
276
|
+
# • Makefile
|
|
277
|
+
# • openlane/Makefile
|
|
278
|
+
|
|
279
|
+
# Update files from upstream
|
|
280
|
+
cf repo update
|
|
281
|
+
# Output:
|
|
282
|
+
# Update results:
|
|
283
|
+
# ✓ Updated: .cf/repo.json
|
|
284
|
+
# ✓ Updated: README.md
|
|
285
|
+
# ✓ Updated: Makefile
|
|
286
|
+
# ✓ Updated: openlane/Makefile
|
|
287
|
+
# Successfully updated 4 file(s)
|
|
288
|
+
# All files updated successfully!
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Use cases:**
|
|
292
|
+
- Keep your project up-to-date with upstream changes
|
|
293
|
+
- Apply bug fixes and improvements from the template repository
|
|
294
|
+
- Sync configuration changes from the upstream project
|
|
295
|
+
- Maintain consistency with the latest project structure
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Submission Workflow and States
|
|
300
|
+
|
|
301
|
+
The CLI tracks your project submission state through the `submission_state` field in `project.json`:
|
|
302
|
+
|
|
303
|
+
### Project States
|
|
304
|
+
|
|
305
|
+
- **"Draft"** - Initial state when you run `cf init`
|
|
306
|
+
- Project is ready for development and testing
|
|
307
|
+
- You can push updates multiple times
|
|
308
|
+
- Project is not yet ready for tapeout processing
|
|
309
|
+
|
|
310
|
+
- **"Final"** - Confirmed state when you run `cf confirm`
|
|
311
|
+
- GDS file is ready to be sent to the foundry for tapeout
|
|
312
|
+
- No further changes should be made to the design
|
|
313
|
+
- Only the project.json is uploaded (not the full project)
|
|
314
|
+
|
|
315
|
+
### Recommended Workflow
|
|
316
|
+
|
|
317
|
+
1. **Development Phase:**
|
|
318
|
+
```bash
|
|
319
|
+
cf init # Creates project with submission_state: "Draft"
|
|
320
|
+
cf push # Upload project files (state remains "Draft")
|
|
321
|
+
# ... make changes to your project ...
|
|
322
|
+
cf push # Upload updated files (state remains "Draft")
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
2. **Review Phase (Optional):**
|
|
326
|
+
```bash
|
|
327
|
+
cf pull # Download results for review (if available)
|
|
328
|
+
cf view-tapeout-report # Review the tapeout report (if available)
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
3. **Final Tapeout Confirmation:**
|
|
332
|
+
```bash
|
|
333
|
+
cf confirm # Confirm current GDS file is ready for foundry tapeout
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**Important:** Only run `cf confirm` when you are completely satisfied with your GDS file and ready to send it to the foundry for tapeout processing. This action cannot be easily undone.
|
|
337
|
+
|
|
219
338
|
---
|
|
220
339
|
|
|
221
340
|
## How the GDS Hash Works
|
|
@@ -303,9 +422,15 @@ cf pull
|
|
|
303
422
|
# ✓ All files downloaded to sftp-output/my_awesome_project
|
|
304
423
|
# ✓ Project config automatically updated
|
|
305
424
|
|
|
306
|
-
#
|
|
425
|
+
# Review the tapeout report (if available)
|
|
307
426
|
cf view-tapeout-report
|
|
308
427
|
# ✓ Opened tapeout report in browser: sftp-output/my_awesome_project/consolidated_reports/consolidated_report.html
|
|
428
|
+
|
|
429
|
+
# When ready, confirm final tapeout (sends GDS to foundry)
|
|
430
|
+
cf confirm
|
|
431
|
+
# ✓ Updated project.json with submission_state = Final
|
|
432
|
+
# ✓ Confirmed project submission: my_awesome_project
|
|
433
|
+
# ✓ Uploaded project.json to incoming/projects/my_awesome_project/.cf/project.json
|
|
309
434
|
```
|
|
310
435
|
|
|
311
436
|
### Advanced Usage
|
|
@@ -326,6 +451,9 @@ cf pull --project-name other_project
|
|
|
326
451
|
# View report for specific project
|
|
327
452
|
cf view-tapeout-report --project-name other_project
|
|
328
453
|
|
|
454
|
+
# Confirm final tapeout for specific project
|
|
455
|
+
cf confirm --project-name other_project
|
|
456
|
+
|
|
329
457
|
# View custom report file
|
|
330
458
|
cf view-tapeout-report --report-path /path/to/custom_report.html
|
|
331
459
|
|
|
@@ -373,6 +501,12 @@ gds/user_project_wrapper.gds.gz # ← Remove this one
|
|
|
373
501
|
- Check that the report exists at the expected location
|
|
374
502
|
- Use `--report-path` to specify a custom report location
|
|
375
503
|
|
|
504
|
+
- **Repository update errors:**
|
|
505
|
+
- Check your internet connection
|
|
506
|
+
- Verify the repository and branch exist
|
|
507
|
+
- Use `--dry-run` to preview changes before applying them
|
|
508
|
+
- Check that the `.cf/repo.json` file exists in the upstream repository
|
|
509
|
+
|
|
376
510
|
- **ModuleNotFoundError:**
|
|
377
511
|
- Upgrade the CLI: `pip install --upgrade chipfoundry-cli`
|
|
378
512
|
|
|
@@ -4,7 +4,7 @@ from chipfoundry_cli.utils import (
|
|
|
4
4
|
collect_project_files, ensure_cf_directory, update_or_create_project_json,
|
|
5
5
|
sftp_connect, upload_with_progress, sftp_ensure_dirs, sftp_download_recursive,
|
|
6
6
|
get_config_path, load_user_config, save_user_config, GDS_TYPE_MAP,
|
|
7
|
-
open_html_in_browser, download_with_progress
|
|
7
|
+
open_html_in_browser, download_with_progress, update_repo_files
|
|
8
8
|
)
|
|
9
9
|
import os
|
|
10
10
|
from pathlib import Path
|
|
@@ -196,7 +196,7 @@ def init(project_root):
|
|
|
196
196
|
project_type = console.input(f"Project type (digital/analog/openframe) (detected: [cyan]{gds_type}[/cyan]): ").strip() or gds_type
|
|
197
197
|
else:
|
|
198
198
|
project_type = console.input("Project type (digital/analog/openframe): ").strip()
|
|
199
|
-
version =
|
|
199
|
+
version = "1" # Start with version 1, will be auto-incremented on push
|
|
200
200
|
# No hash yet, will be filled by push
|
|
201
201
|
data = {
|
|
202
202
|
"project": {
|
|
@@ -204,7 +204,8 @@ def init(project_root):
|
|
|
204
204
|
"type": project_type,
|
|
205
205
|
"user": username,
|
|
206
206
|
"version": version,
|
|
207
|
-
"user_project_wrapper_hash": ""
|
|
207
|
+
"user_project_wrapper_hash": "",
|
|
208
|
+
"submission_state": "Draft"
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
211
|
with open(project_json_path, 'w') as f:
|
|
@@ -770,5 +771,184 @@ def view_tapeout_report(project_name, report_path):
|
|
|
770
771
|
console.print(f"[red]Failed to open tapeout report in browser: {e}[/red]")
|
|
771
772
|
raise click.Abort()
|
|
772
773
|
|
|
774
|
+
@main.command('confirm')
|
|
775
|
+
@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).')
|
|
776
|
+
@click.option('--sftp-host', default=DEFAULT_SFTP_HOST, show_default=True, help='SFTP server hostname.')
|
|
777
|
+
@click.option('--sftp-username', required=False, help='SFTP username (defaults to config).')
|
|
778
|
+
@click.option('--sftp-key', type=click.Path(exists=True, dir_okay=False), help='Path to SFTP private key file (defaults to config).', default=None, show_default=False)
|
|
779
|
+
@click.option('--project-name', help='Project name (e.g., "my_project"). Overrides project.json if exists.')
|
|
780
|
+
def confirm(project_root, sftp_host, sftp_username, sftp_key, project_name):
|
|
781
|
+
"""Confirm project submission by setting submission_state to Final and pushing project.json to SFTP."""
|
|
782
|
+
# If .cf/project.json exists in cwd, use it as default project_root and project_name
|
|
783
|
+
cwd_root, cwd_project_name = get_project_json_from_cwd()
|
|
784
|
+
if not project_root and cwd_root:
|
|
785
|
+
project_root = cwd_root
|
|
786
|
+
if not project_name and cwd_project_name:
|
|
787
|
+
project_name = cwd_project_name
|
|
788
|
+
if not project_root:
|
|
789
|
+
console.print("[bold red]No project root specified and no .cf/project.json found in current directory. Please provide --project-root.[/bold red]")
|
|
790
|
+
raise click.Abort()
|
|
791
|
+
|
|
792
|
+
# Load user config for defaults
|
|
793
|
+
config = load_user_config()
|
|
794
|
+
if not sftp_username:
|
|
795
|
+
sftp_username = config.get("sftp_username")
|
|
796
|
+
if not sftp_username:
|
|
797
|
+
console.print("[bold red]No SFTP username provided and not found in config. Please run 'cf config' or provide --sftp-username.[/bold red]")
|
|
798
|
+
raise click.Abort()
|
|
799
|
+
if not sftp_key:
|
|
800
|
+
sftp_key = config.get("sftp_key")
|
|
801
|
+
|
|
802
|
+
# Always resolve key_path to absolute path if set
|
|
803
|
+
if sftp_key:
|
|
804
|
+
key_path = os.path.abspath(os.path.expanduser(sftp_key))
|
|
805
|
+
else:
|
|
806
|
+
key_path = DEFAULT_SSH_KEY
|
|
807
|
+
|
|
808
|
+
if not os.path.exists(key_path):
|
|
809
|
+
console.print(f"[red]SFTP key file not found: {key_path}[/red]")
|
|
810
|
+
console.print("[yellow]Please run 'cf keygen' to generate a key or 'cf config' to set a custom key path.[/yellow]")
|
|
811
|
+
raise click.Abort()
|
|
812
|
+
|
|
813
|
+
# Load and update project.json
|
|
814
|
+
project_json_path = Path(project_root) / '.cf' / 'project.json'
|
|
815
|
+
if not project_json_path.exists():
|
|
816
|
+
console.print(f"[red]Project configuration not found at {project_json_path}[/red]")
|
|
817
|
+
console.print("[yellow]Please run 'cf init' first to initialize your project.[/yellow]")
|
|
818
|
+
raise click.Abort()
|
|
819
|
+
|
|
820
|
+
# Load existing project.json
|
|
821
|
+
try:
|
|
822
|
+
with open(project_json_path, 'r') as f:
|
|
823
|
+
project_data = json.load(f)
|
|
824
|
+
except Exception as e:
|
|
825
|
+
console.print(f"[red]Failed to read project.json: {e}[/red]")
|
|
826
|
+
raise click.Abort()
|
|
827
|
+
|
|
828
|
+
# Set submission_state to Final
|
|
829
|
+
if "project" not in project_data:
|
|
830
|
+
project_data["project"] = {}
|
|
831
|
+
|
|
832
|
+
project_data["project"]["submission_state"] = "Final"
|
|
833
|
+
|
|
834
|
+
# Save updated project.json
|
|
835
|
+
try:
|
|
836
|
+
with open(project_json_path, 'w') as f:
|
|
837
|
+
json.dump(project_data, f, indent=2)
|
|
838
|
+
console.print("[green]✓ Updated project.json with submission_state = Final[/green]")
|
|
839
|
+
except Exception as e:
|
|
840
|
+
console.print(f"[red]Failed to update project.json: {e}[/red]")
|
|
841
|
+
raise click.Abort()
|
|
842
|
+
|
|
843
|
+
# Get final project name for SFTP upload
|
|
844
|
+
final_project_name = project_name or project_data.get("project", {}).get("name")
|
|
845
|
+
if not final_project_name:
|
|
846
|
+
console.print("[red]No project name found in project.json. Please provide --project-name.[/red]")
|
|
847
|
+
raise click.Abort()
|
|
848
|
+
|
|
849
|
+
# Connect to SFTP and upload project.json
|
|
850
|
+
console.print(f"Connecting to {sftp_host}...")
|
|
851
|
+
transport = None
|
|
852
|
+
try:
|
|
853
|
+
sftp, transport = sftp_connect(
|
|
854
|
+
host=sftp_host,
|
|
855
|
+
username=sftp_username,
|
|
856
|
+
key_path=key_path
|
|
857
|
+
)
|
|
858
|
+
# Ensure the project directory exists before uploading
|
|
859
|
+
sftp_project_dir = f"incoming/projects/{final_project_name}"
|
|
860
|
+
sftp_ensure_dirs(sftp, sftp_project_dir)
|
|
861
|
+
except Exception as e:
|
|
862
|
+
console.print(f"[red]Failed to connect to SFTP: {e}[/red]")
|
|
863
|
+
raise click.Abort()
|
|
864
|
+
|
|
865
|
+
try:
|
|
866
|
+
# Upload only the project.json file
|
|
867
|
+
remote_path = os.path.join(sftp_project_dir, ".cf", "project.json")
|
|
868
|
+
upload_with_progress(
|
|
869
|
+
sftp,
|
|
870
|
+
local_path=str(project_json_path),
|
|
871
|
+
remote_path=remote_path,
|
|
872
|
+
force_overwrite=True # Always overwrite for confirmation
|
|
873
|
+
)
|
|
874
|
+
console.print(f"[green]✓ Confirmed project submission: {final_project_name}[/green]")
|
|
875
|
+
console.print(f"[green]✓ Uploaded project.json to {remote_path}[/green]")
|
|
876
|
+
|
|
877
|
+
except Exception as e:
|
|
878
|
+
console.print(f"[red]Upload failed: {e}[/red]")
|
|
879
|
+
raise click.Abort()
|
|
880
|
+
finally:
|
|
881
|
+
if transport:
|
|
882
|
+
sftp.close()
|
|
883
|
+
transport.close()
|
|
884
|
+
|
|
885
|
+
@main.group('repo')
|
|
886
|
+
def repo_group():
|
|
887
|
+
"""Repository management commands."""
|
|
888
|
+
pass
|
|
889
|
+
|
|
890
|
+
@repo_group.command('update')
|
|
891
|
+
@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).')
|
|
892
|
+
@click.option('--repo-owner', default='chipfoundry', help='GitHub repository owner (default: chipfoundry)')
|
|
893
|
+
@click.option('--repo-name', default='caravel_user_project', help='GitHub repository name (default: caravel_user_project)')
|
|
894
|
+
@click.option('--branch', default='cli-update', help='Branch name containing the repo.json file (default: cli-update)')
|
|
895
|
+
@click.option('--dry-run', is_flag=True, help='Preview changes without updating files')
|
|
896
|
+
def repo_update(project_root, repo_owner, repo_name, branch, dry_run):
|
|
897
|
+
"""Update local repository files from upstream GitHub repository based on .cf/repo.json changes list."""
|
|
898
|
+
# If .cf/project.json exists in cwd, use it as default project_root
|
|
899
|
+
cwd_root, _ = get_project_json_from_cwd()
|
|
900
|
+
if not project_root and cwd_root:
|
|
901
|
+
project_root = cwd_root
|
|
902
|
+
if not project_root:
|
|
903
|
+
project_root = os.getcwd()
|
|
904
|
+
|
|
905
|
+
console.print(f"[bold cyan]Updating repository files from {repo_owner}/{repo_name}@{branch}[/bold cyan]")
|
|
906
|
+
|
|
907
|
+
try:
|
|
908
|
+
if dry_run:
|
|
909
|
+
console.print("[yellow]Dry run mode - no files will be modified[/yellow]")
|
|
910
|
+
# Fetch repo.json to show what would be updated
|
|
911
|
+
from chipfoundry_cli.utils import fetch_github_file
|
|
912
|
+
repo_json_content = fetch_github_file(repo_owner, repo_name, ".cf/repo.json", branch)
|
|
913
|
+
repo_data = json.loads(repo_json_content)
|
|
914
|
+
changes = repo_data.get("changes", [])
|
|
915
|
+
|
|
916
|
+
console.print(f"[cyan]Files that would be updated:[/cyan]")
|
|
917
|
+
console.print(f" • .cf/repo.json (configuration file)")
|
|
918
|
+
for file_path in changes:
|
|
919
|
+
console.print(f" • {file_path}")
|
|
920
|
+
else:
|
|
921
|
+
# Perform the actual update
|
|
922
|
+
results = update_repo_files(project_root, repo_owner, repo_name, branch)
|
|
923
|
+
|
|
924
|
+
if "error" in results:
|
|
925
|
+
console.print(f"[red]Failed to fetch repository information: {results['error']}[/red]")
|
|
926
|
+
raise click.Abort()
|
|
927
|
+
|
|
928
|
+
# Display results
|
|
929
|
+
success_count = 0
|
|
930
|
+
failure_count = 0
|
|
931
|
+
|
|
932
|
+
console.print(f"[cyan]Update results:[/cyan]")
|
|
933
|
+
for file_path, success in results.items():
|
|
934
|
+
if success:
|
|
935
|
+
console.print(f"[green]✓ Updated: {file_path}[/green]")
|
|
936
|
+
success_count += 1
|
|
937
|
+
else:
|
|
938
|
+
console.print(f"[red]✗ Failed: {file_path}[/red]")
|
|
939
|
+
failure_count += 1
|
|
940
|
+
|
|
941
|
+
if success_count > 0:
|
|
942
|
+
console.print(f"[green]Successfully updated {success_count} file(s)[/green]")
|
|
943
|
+
if failure_count > 0:
|
|
944
|
+
console.print(f"[red]Failed to update {failure_count} file(s)[/red]")
|
|
945
|
+
raise click.Abort()
|
|
946
|
+
else:
|
|
947
|
+
console.print("[green]All files updated successfully![/green]")
|
|
948
|
+
|
|
949
|
+
except Exception as e:
|
|
950
|
+
console.print(f"[red]Repository update failed: {e}[/red]")
|
|
951
|
+
raise click.Abort()
|
|
952
|
+
|
|
773
953
|
if __name__ == "__main__":
|
|
774
954
|
main()
|
|
@@ -7,6 +7,7 @@ import hashlib
|
|
|
7
7
|
import paramiko
|
|
8
8
|
from rich.progress import Progress, BarColumn, TextColumn, TimeElapsedColumn, TaskProgressColumn
|
|
9
9
|
import toml
|
|
10
|
+
import httpx
|
|
10
11
|
|
|
11
12
|
REQUIRED_FILES = {
|
|
12
13
|
".cf/project.json": False, # Optional, may not exist
|
|
@@ -158,7 +159,7 @@ def update_or_create_project_json(
|
|
|
158
159
|
) -> str:
|
|
159
160
|
"""
|
|
160
161
|
Update or create project.json in cf_dir. If existing_json_path is given, load and update it.
|
|
161
|
-
Otherwise, create a new one. Always update the user_project_wrapper_hash.
|
|
162
|
+
Otherwise, create a new one. Always update the user_project_wrapper_hash and auto-increment version.
|
|
162
163
|
Returns the path to the updated/created project.json.
|
|
163
164
|
"""
|
|
164
165
|
project_json_path = str(Path(cf_dir) / "project.json")
|
|
@@ -169,14 +170,37 @@ def update_or_create_project_json(
|
|
|
169
170
|
data["project"] = {}
|
|
170
171
|
else:
|
|
171
172
|
data = {"project": {}}
|
|
173
|
+
|
|
174
|
+
# Handle version auto-increment only if GDS hash changed
|
|
175
|
+
current_version = data["project"].get("version")
|
|
176
|
+
current_hash = data["project"].get("user_project_wrapper_hash", "")
|
|
177
|
+
|
|
178
|
+
if current_version is None:
|
|
179
|
+
# No existing version, start with 1
|
|
180
|
+
new_version = "1"
|
|
181
|
+
elif current_hash != hash_val:
|
|
182
|
+
# GDS hash changed, increment version
|
|
183
|
+
try:
|
|
184
|
+
# Convert to int, increment, and convert back to string
|
|
185
|
+
version_num = int(current_version)
|
|
186
|
+
new_version = str(version_num + 1)
|
|
187
|
+
except (ValueError, TypeError):
|
|
188
|
+
# If version is not a valid integer, start from 1
|
|
189
|
+
new_version = "1"
|
|
190
|
+
else:
|
|
191
|
+
# GDS hash unchanged, keep same version
|
|
192
|
+
new_version = current_version
|
|
193
|
+
|
|
172
194
|
# Required fields with defaults
|
|
173
|
-
data["project"]
|
|
195
|
+
data["project"]["version"] = new_version
|
|
174
196
|
data["project"]["user_project_wrapper_hash"] = hash_val
|
|
175
|
-
|
|
176
|
-
|
|
197
|
+
|
|
198
|
+
# Apply CLI overrides (but don't override auto-incremented version)
|
|
199
|
+
for key in ["id", "name", "type", "user"]: # Removed "version" from CLI overrides
|
|
177
200
|
cli_key = f"project_{key}" if key != "user" else "sftp_username"
|
|
178
201
|
if cli_key in cli_overrides and cli_overrides[cli_key] is not None:
|
|
179
202
|
data["project"][key] = cli_overrides[cli_key]
|
|
203
|
+
|
|
180
204
|
save_project_json(project_json_path, data)
|
|
181
205
|
return project_json_path
|
|
182
206
|
|
|
@@ -396,4 +420,97 @@ def open_html_in_browser(html_path: str):
|
|
|
396
420
|
file_url = f"file://{urllib.parse.quote(abs_path)}"
|
|
397
421
|
|
|
398
422
|
# Open in default browser
|
|
399
|
-
webbrowser.open(file_url)
|
|
423
|
+
webbrowser.open(file_url)
|
|
424
|
+
|
|
425
|
+
def fetch_github_file(repo_owner: str, repo_name: str, file_path: str, branch: str = "main") -> str:
|
|
426
|
+
"""
|
|
427
|
+
Fetch a file from a GitHub repository using the GitHub API.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
repo_owner: GitHub repository owner (e.g., "chipfoundry")
|
|
431
|
+
repo_name: GitHub repository name (e.g., "caravel_user_project")
|
|
432
|
+
file_path: Path to the file in the repository (e.g., ".cf/repo.json")
|
|
433
|
+
branch: Branch name (default: "main")
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
File content as string
|
|
437
|
+
|
|
438
|
+
Raises:
|
|
439
|
+
httpx.HTTPError: If the request fails
|
|
440
|
+
"""
|
|
441
|
+
url = f"https://raw.githubusercontent.com/{repo_owner}/{repo_name}/{branch}/{file_path}"
|
|
442
|
+
|
|
443
|
+
with httpx.Client() as client:
|
|
444
|
+
response = client.get(url)
|
|
445
|
+
response.raise_for_status()
|
|
446
|
+
return response.text
|
|
447
|
+
|
|
448
|
+
def download_github_file(repo_owner: str, repo_name: str, file_path: str, local_path: str, branch: str = "main") -> bool:
|
|
449
|
+
"""
|
|
450
|
+
Download a file from a GitHub repository and save it locally.
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
repo_owner: GitHub repository owner
|
|
454
|
+
repo_name: GitHub repository name
|
|
455
|
+
file_path: Path to the file in the repository
|
|
456
|
+
local_path: Local path to save the file
|
|
457
|
+
branch: Branch name (default: "main")
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
True if successful, False otherwise
|
|
461
|
+
"""
|
|
462
|
+
try:
|
|
463
|
+
content = fetch_github_file(repo_owner, repo_name, file_path, branch)
|
|
464
|
+
|
|
465
|
+
# Ensure the local directory exists
|
|
466
|
+
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
|
467
|
+
|
|
468
|
+
# Write the content to the local file
|
|
469
|
+
with open(local_path, 'w', encoding='utf-8') as f:
|
|
470
|
+
f.write(content)
|
|
471
|
+
|
|
472
|
+
return True
|
|
473
|
+
except Exception:
|
|
474
|
+
return False
|
|
475
|
+
|
|
476
|
+
def update_repo_files(project_root: str, repo_owner: str = "chipfoundry", repo_name: str = "caravel_user_project", branch: str = "cli-update") -> Dict[str, bool]:
|
|
477
|
+
"""
|
|
478
|
+
Update local repository files based on the repo.json changes list.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
project_root: Local project root directory
|
|
482
|
+
repo_owner: GitHub repository owner
|
|
483
|
+
repo_name: GitHub repository name
|
|
484
|
+
branch: Branch name containing the repo.json file
|
|
485
|
+
|
|
486
|
+
Returns:
|
|
487
|
+
Dictionary mapping file paths to success status
|
|
488
|
+
"""
|
|
489
|
+
results = {}
|
|
490
|
+
|
|
491
|
+
try:
|
|
492
|
+
# Fetch the repo.json file
|
|
493
|
+
repo_json_content = fetch_github_file(repo_owner, repo_name, ".cf/repo.json", branch)
|
|
494
|
+
repo_data = json.loads(repo_json_content)
|
|
495
|
+
|
|
496
|
+
# Save the repo.json file to local .cf directory
|
|
497
|
+
cf_dir = os.path.join(project_root, ".cf")
|
|
498
|
+
os.makedirs(cf_dir, exist_ok=True)
|
|
499
|
+
local_repo_json_path = os.path.join(cf_dir, "repo.json")
|
|
500
|
+
|
|
501
|
+
with open(local_repo_json_path, 'w', encoding='utf-8') as f:
|
|
502
|
+
f.write(repo_json_content)
|
|
503
|
+
results[".cf/repo.json"] = True
|
|
504
|
+
|
|
505
|
+
changes = repo_data.get("changes", [])
|
|
506
|
+
|
|
507
|
+
for file_path in changes:
|
|
508
|
+
local_file_path = os.path.join(project_root, file_path)
|
|
509
|
+
success = download_github_file(repo_owner, repo_name, file_path, local_file_path, branch)
|
|
510
|
+
results[file_path] = success
|
|
511
|
+
|
|
512
|
+
except Exception as e:
|
|
513
|
+
# If we can't fetch the repo.json, return empty results
|
|
514
|
+
results["error"] = str(e)
|
|
515
|
+
|
|
516
|
+
return results
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "chipfoundry-cli"
|
|
3
|
-
version = "1.0
|
|
3
|
+
version = "1.1.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"
|
|
@@ -15,6 +15,7 @@ click = ">=8.0.0,<9"
|
|
|
15
15
|
rich = ">=13,<14"
|
|
16
16
|
paramiko = ">=3.0.0,<4"
|
|
17
17
|
toml = ">=0.10,<1.0"
|
|
18
|
+
httpx = ">=0.24.0,<1.0"
|
|
18
19
|
|
|
19
20
|
[tool.poetry.dev-dependencies]
|
|
20
21
|
wheel = "*"
|
|
@@ -27,4 +28,4 @@ cf = "chipfoundry_cli.main:main"
|
|
|
27
28
|
|
|
28
29
|
[build-system]
|
|
29
30
|
requires = ["poetry-core>=1.0.0"]
|
|
30
|
-
build-backend = "poetry.core.masonry.api"
|
|
31
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
File without changes
|